Quais são todos os comportamentos indefinidos comuns que um programador C ++ deve saber? [fechadas]
-
21-08-2019 - |
Pergunta
O que são todos os comportamentos indefinidos comuns que um programador C ++ deve saber?
Say, como:
a[i] = i++;
Solução
Pointer
- desreferenciação um
NULL
ponteiro - Dereferencing um ponteiro retornado por um "novo" alocação de tamanho zero
- Usando ponteiros para objetos cuja vida útil tenha terminado (por exemplo, pilha alocados objetos ou objetos excluídos)
- Dereferencing um ponteiro que ainda não foi definitivamente inicializado
- Performing aritmética ponteiro que se obtém um resultado fora dos limites (acima ou abaixo) de uma matriz.
- Dereferencing o ponteiro numa localização para além da extremidade de uma matriz.
- Conversão de ponteiros para objetos de tipos incompatíveis
- Usando
memcpy
para copiar sobreposição buffers .
buffer overflows
- a leitura ou escrita de um objecto ou da matriz em um deslocamento que é negativa, ou para além do tamanho do referido objecto (pilha / pilha overflow)
Integer Overflows
- Assinado integer overflow
- Ao avaliar uma expressão que não está definido matematicamente
- Esquerda de mudança de valores por um valor negativo (turnos direito por valores negativos são a implementação definido)
- Mudando os valores por uma quantidade maior do que ou igual ao número de bits no número (por exemplo
int64_t i = 1; i <<= 72
é indefinido)
Tipos, Gesso e Const
- Fundição um valor numérico para um valor que não pode ser representado pelo tipo de alvo (quer directamente ou através de static_cast)
- Usando uma variável automática antes de ser definitivamente atribuído (por exemplo,
int i; i++; cout << i;
) - Usando o valor de qualquer objeto do tipo diferente de
volatile
ousig_atomic_t
na recepção de um sinal - A tentativa de modificar um literal string ou qualquer outro objeto const durante sua vida útil
- A concatenação de um estreito com uma ampla literal de cadeia durante a pré-processamento
Função e Template
- Não retornar um valor de uma função que retorna valor (direta ou por que flui fora de um try-bloco)
- várias definições diferentes para a mesma entidade (classe, modelo, enumeração, função inline, a função membro estático, etc.)
- recursão infinita na instanciação de modelos
- Chamar uma função usando diferentes parâmetros ou ligação com os parâmetros e ligação que a função é definida como a utilização.
OOP
- destruições em cascata de objetos com duração de armazenagem estática
- O resultado da atribuição de objectos parcialmente sobrepostos
- Recursively re-entrar uma função durante a inicialização de seus objetos estáticos
- Fazer chamadas de função virtual para funções virtuais puras de um objeto de seu construtor ou destrutor
- No que se refere aos membros nonstatic de objetos que não foram construídos ou já tenham sido destruído
arquivo de origem e pré-processamento
- Um arquivo não-vazia fonte que não termine com uma mudança de linha, ou extremidades com uma barra invertida (antes C ++ 11)
- Uma barra invertida seguida por um caractere que não faz parte dos códigos de escape especificados em uma cadeia de caracteres ou constante (isto é implementação-definido em C ++ 11).
- Exceder limites de implementação (número de blocos aninhados, número de funções em um programa, espaço de pilha disponível ...)
- valores numéricos pré-processador que não podem ser representados por um
long int
- Pré-processamento directiva no lado esquerdo de uma função semelhante a definição de macro
- dinamicamente gerar o definido token em uma expressão
#if
Para ser classificado
- Chamada de saída durante a destruição de um programa com duração de armazenagem estática
Outras dicas
A fim de que os parâmetros de função são avaliados é não especificado comportamento . (Isso não vai fazer o seu programa falhar, explodir, ou ordem pizza ... ao contrário de indefinido comportamento .)
A única exigência é que todos os parâmetros devem ser completamente avaliada antes que a função é chamada.
Este:
// The simple obvious one.
callFunc(getA(),getB());
Pode ser equivalente a esta:
int a = getA();
int b = getB();
callFunc(a,b);
Ou este:
int b = getB();
int a = getA();
callFunc(a,b);
Pode ser qualquer um; é até o compilador. O resultado pode ser importante, dependendo dos efeitos colaterais.
O compilador é livre para reordenar as partes avaliação de uma expressão (assumindo que o significado é inalterado).
A partir da pergunta original:
a[i] = i++;
// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)
// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:
int rhs = i++;
int lhs& = a[i];
lhs = rhs;
// or
int lhs& = a[i];
int rhs = i++;
lhs = rhs;
dobro verificado bloqueio. E um erro fácil de fazer.
A* a = new A("plop");
// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'
// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.
// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
a = new A("Plop"); // (Point A).
}
}
a->doStuff();
// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
// Remember (c) has been done thus 'a' is not NULL.
// But the memory has not been initialized.
// Thread 2 now executes doStuff() on an uninitialized variable.
// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
A* tmp = new A("Plop"); // (Point A).
a = tmp;
}
}
a->doStuff();
// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.
O meu favorito é "recursão infinita na instanciação de modelos", porque eu acredito que é o único onde o comportamento indefinido ocorre em tempo de compilação.
Atribuir a uma constante após a remoção const
ness usando const_cast<>
:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
Além de comportamento indefinido , há também o comportamento igualmente desagradável definido pela implementação .
comportamento indefinido ocorre quando um programa faz algo cujo resultado não é especificado pela norma.
comportamento definido pela implementação é uma ação por um programa cujo resultado não é definido pela norma, mas que a implementação é necessária para documento. Um exemplo é "literais de caracteres multibyte", de pergunta Stack Overflow Existe um compilador C que não consegue compilar este? .
comportamento definido pela implementação única morde você quando você começar a portar (mas o upgrade para nova versão do compilador também está portando!)
variáveis ??só pode ser actualizada de vez em uma expressão (tecnicamente, uma vez entre os pontos de sequência).
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
Uma compreensão básica dos diferentes limites ambientais. A lista completa na secção 5.2.4.1 da especificação C. Aqui estão alguns;
- 127 parâmetros em uma função de fi nição
- 127 argumentos em uma chamada de função
- 127 parâmetros em uma macro de definição
- 127 argumentos em uma chamada de macro
- 4095 caracteres em uma linha fonte lógico
- 4095 caracteres em uma cadeia de caracteres literal string literal ou larga (depois concatenação)
- 65535 bytes em um objeto (em um ambiente hospedado apenas)
- níveis 15nesting para #included fi les
- 1023 rótulos de caso para um interruptor declaração (excluindo aqueles para instruções switch anynested)
Eu estava realmente um pouco surpreso com o limite de 1023 casos de etiquetas para uma instrução switch, eu posso prever que ser ultrapassado por código / lex / analisadores gerados bastante easially.
Se estes limites forem ultrapassados, você tem um comportamento indefinido (travamentos, falhas de segurança, etc ...).
Certo, eu sei que isto é a partir da especificação C, mas as ações C ++ esses suportes básicos.
Usando memcpy
para copiar entre sobreposição regiões de memória. Por exemplo:
char a[256] = {};
memcpy(a, a, sizeof(a));
O comportamento não está definida de acordo com o padrão C, o qual é absorvido pela C ++ 03 padrão.
7.21.2.1 A função memcpy
Sinopse
1 / #include void * memcpy (void * restringir s1, const void * restringir s2, size_t n);
Descrição
2 / A função memcpy cópias n caracteres do objeto apontado por s2 para o objecto apontada por s1. Se a cópia ocorre entre objetos que se sobrepõem, o comportamento é indefinido. Devoluções 3 A função memcpy retorna o valor de s1.
7.21.2.2 A função memmove
Sinopse
1 #include void * memmove (void * s1, const void * s2, size_t n);
Descrição
2 A função memmove cópias n caracteres do objeto apontado por s2 para o objeto apontado por s1. Copiar se passa como se o n caracteres do objeto apontado por s2 são primeiro copiados para uma matriz temporária de n caracteres que não se sobrepõe à objectos apontada por S1 e S2, e, em seguida, os n caracteres do temporária matriz são copiados para o objeto apontado por s1. Retorna
3 A função memmove retorna o valor de s1.
O único tipo para o qual C ++ garante um tamanho é char
. E o tamanho é 1. O tamanho de todos os outros tipos é dependente de plataforma.
Namespace de nível objetos em um diferentes unidades de compilação nunca deve dependem uns dos outros para inicialização, porque a sua ordem de inicialização é indefinido.