Quais são os comportamentos indefinidos comuns que um programador de C ++ deve conhecer?
Diga, como:
a[i] = i++;
Quais são os comportamentos indefinidos comuns que um programador de C ++ deve conhecer?
Diga, como:
a[i] = i++;
Respostas:
NULL
ponteiromemcpy
para copiar buffers sobrepostos .int64_t i = 1; i <<= 72
é indefinido)int i; i++; cout << i;
)volatile
ou sig_atomic_t
ao receber um sinallong int
#if
expressãoA ordem em que os parâmetros de função são avaliados é um comportamento não especificado . (Isso não fará o seu programa travar, explodir ou pedir pizza ... diferente do comportamento indefinido .)
O único requisito é que todos os parâmetros sejam totalmente avaliados antes que a função seja chamada.
Este:
// The simple obvious one.
callFunc(getA(),getB());
Pode ser equivalente a isso:
int a = getA();
int b = getB();
callFunc(a,b);
Ou isto:
int b = getB();
int a = getA();
callFunc(a,b);
Pode ser também; depende do compilador. O resultado pode importar, dependendo dos efeitos colaterais.
O compilador pode reordenar as partes da avaliação de uma expressão (assumindo que o significado não seja alterado).
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;
Bloqueio verificado duas vezes. E um erro fácil de cometer.
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 acredito que é o único onde o comportamento indefinido ocorre no momento da compilação.
Além do comportamento indefinido , há também o comportamento definido pela implementação igualmente desagradável .
O comportamento indefinido ocorre quando um programa faz algo cujo resultado não é especificado pelo padrão.
O comportamento definido pela implementação é uma ação de um programa cujo resultado não é definido pelo padrão, mas que a implementação é requerida para documentar. Um exemplo é "Literais de caracteres multibyte", da questão Stack Overflow. Existe um compilador C que falha ao compilar isso? .
O comportamento definido pela implementação só o incomoda quando você começa a portar (mas a atualização para a nova versão do compilador também está portando!)
As variáveis podem ser atualizadas apenas uma 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 vários limites ambientais. A lista completa está na seção 5.2.4.1 da especificação C. Aqui estão alguns;
Na verdade, fiquei um pouco surpreso com o limite de 1023 rótulos de caso para uma instrução switch, posso prever que sendo excedido o código / lex / parsers gerado com bastante facilidade.
Se esses limites forem excedidos, você terá um comportamento indefinido (falhas, falhas de segurança, etc.).
Certo, eu sei que isso é da especificação C, mas o C ++ compartilha esses suportes básicos.
Usando memcpy
para copiar entre regiões de memória sobrepostas. Por exemplo:
char a[256] = {};
memcpy(a, a, sizeof(a));
O comportamento é indefinido de acordo com o Padrão C, incluído no Padrão C ++ 03.
Sinopse
1 / #include void * memcpy (void * restrita s1, const void * restrita s2, size_t n);
Descrição
2 / A função memcpy copia n caracteres do objeto apontado por s2 para o objeto apontado por s1. Se a cópia ocorrer entre objetos que se sobrepõem, o comportamento é indefinido. Retorna 3 A função memcpy retorna o valor de s1.
Sinopse
1 #include void * memmove (void * s1, const void * s2, size_t n);
Descrição
2 A função memmove copia n caracteres do objeto apontado por s2 para o objeto apontado por s1. A cópia ocorre como se os n caracteres do objeto apontado por s2 fossem primeiro copiados para uma matriz temporária de n caracteres que não se sobrepusessem aos objetos apontados por s1 e s2 e, em seguida, os n caracteres da matriz temporária fossem copiados para o objeto apontado por s1. Devoluções
3 A função memmove retorna o valor de s1.
O único tipo para o qual o C ++ garante um tamanho é char
. E o tamanho é 1. O tamanho de todos os outros tipos depende da plataforma.