É válido copiar uma estrutura em que alguns dos membros não são inicializados?
Suspeito que seja um comportamento indefinido, mas, se for o caso, torna perigoso deixar membros não inicializados em uma estrutura (mesmo que esses membros nunca sejam usados diretamente). Então, eu me pergunto se há algo no padrão que permita isso.
Por exemplo, isso é válido?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
c++
initialization
copy-constructor
undefined-behavior
Tomek Czajka
fonte
fonte
Respostas:
Sim, se o membro não inicializado não for um tipo de caractere estreito não assinado ou
std::byte
, copiar uma estrutura contendo esse valor indeterminado com o construtor de cópias definido implicitamente é um comportamento tecnicamente indefinido, pois é para copiar uma variável com valor indeterminado do mesmo tipo, porque de [dcl.init] / 12 .Isso se aplica aqui, porque o construtor de cópias gerado implicitamente é, exceto
union
s, definido para copiar cada membro individualmente como se fosse pela inicialização direta, consulte [class.copy.ctor] / 4 .Isso também está sujeito ao problema ativo do CWG 2264 .
Suponho que, na prática, você não terá nenhum problema com isso.
Se você quiser ter 100% de certeza, o uso
std::memcpy
sempre terá um comportamento bem definido se o tipo for trivialmente copiável , mesmo se os membros tiverem um valor indeterminado.Além desses problemas, você deve sempre inicializar os membros da sua classe adequadamente com um valor especificado na construção de qualquer maneira, supondo que você não exija que a classe tenha um construtor padrão trivial . Você pode fazer isso facilmente usando a sintaxe padrão do inicializador de membros para, por exemplo, inicializar com valor os membros:
fonte
memcpy
, mesmo para tipos trivialmente copiáveis. A única exceção são as uniões, para as quais o construtor de cópia implícito copia a representação do objeto como se estivesse pormemcpy
.Em geral, copiar dados não inicializados é um comportamento indefinido, pois esses dados podem estar em um estado de interceptação. Citando esta página:
NaNs de sinalização são possíveis para tipos de ponto flutuante e, em algumas plataformas, números inteiros podem ter representações de interceptação.
No entanto, para tipos trivialmente copiáveis , é possível usar
memcpy
para copiar a representação bruta do objeto. Fazer isso é seguro, pois o valor do objeto não é interpretado e, em vez disso, a sequência de bytes brutos da representação do objeto é copiada.fonte
unsigned char[64]
)? Tratar os bytes de uma estrutura como tendo valores não especificados pode impedir desnecessariamente a otimização, mas exigir que os programadores preencham manualmente a matriz com valores inúteis impediria ainda mais a eficiência.Em alguns casos, como o descrito, o Padrão C ++ permite que os compiladores processem construções da maneira que seus clientes acharem mais útil, sem exigir que o comportamento seja previsível. Em outras palavras, essas construções invocam "comportamento indefinido". Isso não implica, no entanto, que tais construções sejam "proibidas", uma vez que o Padrão C ++ renuncia explicitamente à jurisdição sobre o que programas bem formados são "permitidos". Embora eu não conheça nenhum documento publicado do Rationale para o C ++ Standard, o fato de descrever o Comportamento indefinido da mesma forma que o C89 sugere o significado pretendido é semelhante: "O comportamento indefinido dá ao implementador licença para não detectar certos erros de programa difíceis. diagnosticar.
Existem muitas situações em que a maneira mais eficiente de processar algo envolveria escrever as partes de uma estrutura com a qual o código downstream se importaria, enquanto omitir aquelas que o código downstream não se importaria. Exigir que os programas inicializem todos os membros de uma estrutura, incluindo aqueles com os quais nada se importa, impediria desnecessariamente a eficiência.
Além disso, há algumas situações em que pode ser mais eficiente que dados não inicializados se comportem de maneira não determinística. Por exemplo, dado:
se o código downstream não se importar com os valores de quaisquer elementos
x.dat
ouy.dat
cujos índices não foram listadosarr
, o código poderá ser otimizado para:Essa melhoria na eficiência não seria possível se os programadores precisassem escrever explicitamente todos os elementos do
temp.dat
, incluindo aqueles que não se importam com o downstream, antes de copiá-lo.Por outro lado, existem algumas aplicações em que é importante evitar a possibilidade de vazamento de dados. Nesses aplicativos, pode ser útil ter uma versão do código instrumentada para interceptar qualquer tentativa de copiar o armazenamento não inicializado, sem levar em consideração se o código downstream o analisaria, ou pode ser útil ter uma garantia de implementação que qualquer armazenamento cujo conteúdo pudesse ser vazado seria zerado ou substituído por dados não confidenciais.
Pelo que posso dizer, o Padrão C ++ não tenta dizer que qualquer um desses comportamentos é suficientemente mais útil que o outro para justificar sua exigência. Ironicamente, essa falta de especificação pode ter como objetivo facilitar a otimização, mas se os programadores não puderem explorar nenhum tipo de garantia comportamental fraca, qualquer otimização será negada.
fonte
Como todos os membros do
Data
são de tipos primitivos,data2
obterá a "cópia bit a bit" exata de todos os membros dodata
. Portanto, o valor dedata2.b
será exatamente o mesmo que o valor dedata.b
. No entanto, o valor exato dodata.b
não pode ser previsto, porque você não o inicializou explicitamente. Isso dependerá dos valores dos bytes na região de memória alocada para odata
.fonte
std::memcpy
. Nada disso impede o uso destd::memcpy
oustd::memmove
. Apenas impede o uso do construtor de cópia implícita.