Est-il valide de copier une structure dont certains membres ne sont pas initialisés?
Je soupçonne que c'est un comportement indéfini, mais si c'est le cas, cela rend très dangereux le fait de laisser des membres non initialisés dans une structure (même si ces membres ne sont jamais utilisés directement). Je me demande donc s'il y a quelque chose dans la norme qui le permet.
Par exemple, est-ce valable?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
c++
initialization
copy-constructor
undefined-behavior
Tomek Czajka
la source
la source
Réponses:
Oui, si le membre non initialisé n'est pas un type de caractère étroit non signé ou
std::byte
, alors la copie d'une structure contenant cette valeur indéterminée avec le constructeur de copie défini implicitement est un comportement techniquement indéfini, comme pour la copie d'une variable avec une valeur indéterminée du même type, car de [dcl.init] / 12 .Cela s'applique ici, car le constructeur de copie généré implicitement est, à l'exception de
union
s, défini pour copier chaque membre individuellement comme si par initialisation directe, voir [class.copy.ctor] / 4 .Cela fait également l'objet du numéro CWG 2264 actif .
Je suppose qu'en pratique, vous n'aurez aucun problème avec cela.
Si vous voulez être sûr à 100%, l'utilisation a
std::memcpy
toujours un comportement bien défini si le type est trivialement copiable , même si les membres ont une valeur indéterminée.Ces problèmes mis à part, vous devez toujours initialiser correctement vos membres de classe avec une valeur spécifiée lors de la construction, en supposant que vous n'avez pas besoin que la classe ait un constructeur par défaut trivial . Vous pouvez le faire facilement en utilisant la syntaxe d'initialisation des membres par défaut pour par exemple initialiser les membres par valeur:
la source
memcpy
, même pour les types trivialement copiables. La seule exception concerne les unions, pour lesquelles le constructeur de copie implicite copie la représentation d'objet comme si parmemcpy
.En général, la copie de données non initialisées est un comportement non défini car ces données peuvent être dans un état de recouvrement. Citant cette page:
Les NaN de signalisation sont possibles pour les types à virgule flottante, et sur certaines plates-formes, les entiers peuvent avoir des représentations d' interruption .
Cependant, pour les types trivialement copiables, il est possible d'utiliser
memcpy
pour copier la représentation brute de l'objet. Cette opération est sûre car la valeur de l'objet n'est pas interprétée et la séquence d'octets bruts de la représentation de l'objet est copiée.la source
unsigned char[64]
)? Traiter les octets d'une structure comme ayant des valeurs non spécifiées pourrait entraver inutilement l'optimisation, mais obliger les programmeurs à remplir manuellement le tableau avec des valeurs inutiles entraverait encore plus l'efficacité.Dans certains cas, comme celui décrit, la norme C ++ permet aux compilateurs de traiter les constructions de la manière que leurs clients trouveraient la plus utile, sans exiger que ce comportement soit prévisible. En d'autres termes, ces constructions invoquent un "comportement indéfini". Cela n'implique pas, cependant, que de telles constructions sont censées être "interdites" puisque la norme C ++ renonce explicitement à la juridiction sur ce que les programmes bien formés sont "autorisés" à faire. Bien que je ne sois au courant d'aucun document de justification publié pour la norme C ++, le fait qu'il décrit un comportement indéfini, tout comme le fait C89, suggère que la signification voulue est similaire: diagnostiquer.
Il existe de nombreuses situations où le moyen le plus efficace de traiter quelque chose impliquerait d'écrire les parties d'une structure dont le code en aval va se soucier, tout en omettant celles dont le code en aval ne se souciera pas. Exiger que les programmes initialisent tous les membres d'une structure, y compris ceux dont personne ne se souciera jamais, entraverait inutilement l'efficacité.
De plus, dans certaines situations, il peut être plus efficace de faire en sorte que les données non initialisées se comportent de manière non déterministe. Par exemple, étant donné:
si le code en aval ne se soucie pas des valeurs des éléments de
x.dat
ouy.dat
dont les indices ne sont pas répertoriés dansarr
, le code peut être optimisé pour:Cette amélioration de l'efficacité ne serait pas possible si les programmeurs étaient tenus d'écrire explicitement tous les éléments de
temp.dat
, y compris ceux en aval qui ne se soucient pas, avant de le copier.D'un autre côté, il existe certaines applications où il est important d'éviter la possibilité de fuite de données. Dans de telles applications, il peut être utile d'avoir une version du code qui est instrumentée pour intercepter toute tentative de copier le stockage non initialisé sans se soucier de savoir si le code en aval le regarderait, ou il pourrait être utile d'avoir une garantie d'implémentation que tout stockage dont le contenu pourrait être divulgué serait mis à zéro ou remplacé par des données non confidentielles.
D'après ce que je peux dire, le standard C ++ ne tente pas de dire que l'un de ces comportements est suffisamment plus utile que l'autre pour justifier son mandat. Ironiquement, ce manque de spécification peut être destiné à faciliter l'optimisation, mais si les programmeurs ne peuvent exploiter aucune sorte de garanties comportementales faibles, toutes les optimisations seront annulées.
la source
Étant donné que tous les membres de
Data
sont de types primitifs,data2
obtiendra une "copie bit par bit" exacte de tous les membres dedata
. La valeur dedata2.b
sera donc exactement la même que la valeur dedata.b
. Cependant, la valeur exacte dedata.b
ne peut pas être prédite, car vous ne l'avez pas initialisée explicitement. Cela dépendra des valeurs des octets dans la région de mémoire allouée pour ledata
.la source
std::memcpy
. Rien de tout cela n'empêche d'utiliserstd::memcpy
oustd::memmove
. Il empêche uniquement l'utilisation du constructeur de copie implicite.