Qu'arrive-t-il à une variable déclarée non initialisée en C? A-t-elle une valeur?

Si en C j'écris:

int num;

avant d'attribuer quoi que ce soit à num , la valeur de num est-elle indéterminée?

122

10 réponses

variables Statiques (fichier portée et la fonction statique) sont initialisés à zéro:

int x; // zero
int y = 0; // also zero

void foo() {
    static int x; // also zero
}

les variables Non statiques (variables locales) sont indéterminé . Leur lecture avant l'attribution d'une valeur entraîne un comportement non défini.

void foo() {
    int x;
    printf("%d", x); // the compiler is free to crash here
}

dans la pratique, ils ont tendance à avoir juste une certaine valeur absurde dans là initialement - certains compilateurs peuvent même mettre des valeurs spécifiques, fixes pour le rendre évident en regardant dans un débogueur - mais à proprement parler, le compilateur est libre de faire n'importe quoi, de s'écraser à invoquer démons à travers vos passages nasaux .

quant à la raison pour laquelle il s'agit d'un comportement non défini au lieu de simplement" valeur non définie/arbitraire", il y a un certain nombre d'architectures CPU qui ont des bits de drapeau supplémentaires dans leur représentation pour divers types. Un exemple moderne serait l'Itanium, qui a un "Pas une Chose" bit dans ses registres ; bien sûr, les rédacteurs de la norme C envisageaient certaines architectures plus anciennes.

essayer de travailler avec une valeur avec ces bits de drapeau peut entraîner une exception CPU dans une opération que vraiment ne devrait pas échouer (par exemple, l'addition entière, ou l'assignation à une autre variable). Et si vous allez et laissez une variable uninitialized, le compilateur pourrait ramasser quelques ordures au hasard avec ces bits de drapeau mis - ce qui signifie toucher que la variable uninitialized peut être mortel.

166
répondu bdonlan 2009-10-21 05:40:28

0 si statique ou globale, indéterminée si la classe de stockage est auto

c a toujours été très spécifique sur les valeurs initiales des objets. Si global ou static , ils seront mis à zéro. Si auto , la valeur est indéterminé .

cela a été le cas dans les compilateurs pré-C89 et cela a été spécifié par K&R et dans le rapport C original de DMR.

tel était le cas en C89, voir section 6.5.7 initialisation .

si un objet qui a une durée de stockage n'est pas initialisé explicitement, sa valeur est indéterminé. Si un objet qui a la durée de stockage statique n'est pas initialisé explicitement, il est initialisé implicitement comme si chaque membre qui a le type arithmétique ont été attribué 0 et chaque membre qui a le type de pointeur a été assigné à un null pointeur constant.

Ce fut le cas en C99, voir section 6.7.8 initialisation .

si un objet qui a une durée de stockage n'est pas initialisé explicitement, sa valeur est indéterminé. Si un objet qui a la durée de stockage statique n'est pas initialisé explicitement, puis:

- si elle a le type de pointeur, il est initialisé à un pointeur nul;

- s'il a arithmétique type de, il est initialisé à (positif ou non signé) zéro;

- s'il s'agit d'un globale, chaque membre est initialisé (de manière récursive) conformément à ces

- s'il s'agit d'une union, la première nommé membre est initialisé (de manière récursive) conformément à ces règle.

quant à ce que signifie exactement indéterminé , Je ne suis pas sûr pour C89, C99 dit:

3.17.2

une valeur indéterminée


, soit une valeur indéterminée ou un piège représentation

mais indépendamment de ce que les normes disent, dans la vie réelle, chaque page de pile commence effectivement comme zéro, mais quand votre programme regarde n'importe quelles valeurs de classe de stockage auto , il voit ce qui a été laissé derrière par votre propre programme quand il a utilisé pour la dernière fois ces adresses de pile. Si vous attribuez beaucoup de auto tableaux vous les verrez éventuellement commencer proprement avec des zéros.

pourquoi est-ce ainsi? Une réponse différente de SO traite de cette question, voir: https://stackoverflow.com/a/2091505/140740

53
répondu DigitalRoss 2017-05-23 11:47:19

Cela dépend de la durée de stockage de la variable. Une variable avec une durée de stockage statique est toujours initialisée implicitement avec zéro.

comme pour les variables automatiques (locales), une variable non initialisée a valeur indéterminée . La valeur indéterminée, entre autres choses, signifie que quelle que soit la "valeur" que vous pourriez "voir" dans cette variable n'est pas seulement imprévisible, il n'est même pas garanti d'être stable . Exemple, en pratique (c'est-à-dire en ignorant L'UB pendant une seconde) ce code

int num;
int a = num;
int b = num;

ne garantit pas que les variables a et b recevront des valeurs identiques. Il est intéressant de noter qu'il ne s'agit pas d'un concept théorique pédant, ce qui se produit facilement dans la pratique en conséquence de l'optimisation.

donc, en général, la réponse populaire que" il est initialisé avec ce que les ordures étaient dans la mémoire " n'est même pas du tout correcte. Le comportement de la variable non initialisée est différent de celui d'une variable initialisée avec les ordures.

10
répondu AnT 2011-09-22 18:27:38

Ubuntu 15.10, Kernel 4.2.0, x86-64, GCC 5.2.1 exemple

assez de normes, regardons une mise en œuvre :-)

variable locale

les Normes: un comportement indéfini.

Mise en œuvre

: le programme alloue de l'espace de pile, et ne déplace jamais rien à cette adresse, donc ce qui était là auparavant est utilisé.

#include <stdio.h>
int main() {
    int i;
    printf("%d\n", i);
}

compiler avec:

gcc -O0 -std=c99 a.c

sorties:

0

et se décompose avec:

objdump -dr a.out

à:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       48 83 ec 10             sub    "151940920"x10,%rsp
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax
  400541:       89 c6                   mov    %eax,%esi
  400543:       bf e4 05 40 00          mov    "151940920"x4005e4,%edi
  400548:       b8 00 00 00 00          mov    "151940920"x0,%eax
  40054d:       e8 be fe ff ff          callq  400410 <printf@plt>
  400552:       b8 00 00 00 00          mov    "151940920"x0,%eax
  400557:       c9                      leaveq
  400558:       c3                      retq

D'après notre connaissance des conventions d'appel x86-64:

  • %rdi est le premier argument printf, donc la chaîne "%d\n" à l'adresse 0x4005e4

  • %rsi est le deuxième argument printf, donc i .

    il vient de -0x4(%rbp) , qui est la première variable locale de 4 octets.

    à ce point, rbp est dans la première page de la pile a été attribué par le noyau, donc pour comprendre cette valeur nous devrions regarder dans le code du noyau et trouver ce qu'il définit à.

    TODO ne le noyau jeu de mémoire pour quelque chose avant de le réutiliser pour d'autres processus, lorsqu'un processus meurt? Si non, le nouveau processus serait capable de lire la mémoire des autres programmes, les fuites de données. Voir: les valeurs non initialisées représentent-elles un risque pour la sécurité?

nous pouvons aussi jouer avec nos propres modifications de pile et écrire des choses amusantes comme:

#include <assert.h>

int f() {
    int i = 13;
    return i;
}

int g() {
    int i;
    return i;
}

int main() {
    f();
    assert(g() == 13);
}

variables Globales

normes: 0

mise en Œuvre: .bss section.

#include <stdio.h>
int i;
int main() {
    printf("%d\n", i);
}

gcc -00 -std=c99 a.c

compile:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       8b 05 04 0b 20 00       mov    0x200b04(%rip),%eax        # 601044 <i>
  400540:       89 c6                   mov    %eax,%esi
  400542:       bf e4 05 40 00          mov    "151970920"x4005e4,%edi
  400547:       b8 00 00 00 00          mov    "151970920"x0,%eax
  40054c:       e8 bf fe ff ff          callq  400410 <printf@plt>
  400551:       b8 00 00 00 00          mov    "151970920"x0,%eax
  400556:       5d                      pop    %rbp
  400557:       c3                      retq
  400558:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40055f:       00

# 601044 <i> dit que i est à l'adresse 0x601044 et:

readelf -SW a.out

contient:

[25] .bss              NOBITS          0000000000601040 001040 000008 00  WA  0   0  4

qui dit 0x601044 est au milieu de la section .bss , qui commence à 0x601040 et est de 8 octets long.

le ELF standard garantit alors que la section nommée .bss est complètement remplie de zéros:

.bss cette section contient des données non initialisées qui contribuent à la image mémoire du programme. Par définition, le système s'initialise le données avec zéros lorsque le programme commence à fonctionner. La section occu- pies pas d'espace de fichier, comme indiqué par le type de section, SHT_NOBITS .

de plus, le type SHT_NOBITS est efficace et n'occupe pas de place sur le fichier exécutable:

sh_size Ce membre donne la taille en octets. À moins que la sec- le type de construction est SHT_NOBITS , la section occupe sh_size octets dans le fichier. Une section de type SHT_NOBITS peut avoir un non-zéro taille, mais il n'occupe pas de place dans le fichier.

alors c'est au noyau Linux de mettre à zéro cette région de mémoire lors du chargement du programme en mémoire quand il est lancé.

5

ça dépend. Si cette définition est globale (en dehors de toute fonction) alors num sera initialisé à zéro. Si elle est locale (à l'intérieur d'une fonction) alors sa valeur est indéterminée. En théorie, même en essayant de lire la valeur a un comportement non défini -- C permet la possibilité de bits qui ne contribuent pas à la valeur, mais doivent être définis de manière spécifique pour que vous puissiez même obtenir des résultats définis en lisant la variable.

3
répondu Jerry Coffin 2009-10-20 21:28:39

La base de la réponse est, oui, il n'est pas défini.

si vous voyez un comportement étrange à cause de cela, cela peut dépendre de l'endroit où il est déclaré. Si dans une fonction sur la pile, le contenu sera plus que probablement différent chaque fois que la fonction est appelée. S'il s'agit d'une portée statique ou d'un module, elle n'est pas définie mais ne changera pas.

1
répondu simon 2009-10-20 21:30:14

si la classe de stockage est statique ou globale alors pendant le chargement, le BSS initialise la variable ou l'emplacement de mémoire(ML) à 0 à moins que la variable soit initialement assignée une certaine valeur. Dans le cas de variables locales non initialisées, la représentation du piège est assignée à l'emplacement de la mémoire. Donc, si l'un de vos registres contenant des informations importantes est écrasé par compilateur le programme peut planter.

mais certains compilateurs peuvent avoir un mécanisme pour éviter une telle problème.

je travaillais avec la série nec v850 quand j'ai réalisé Qu'il y a une représentation de trap qui a des motifs de bits qui représentent des valeurs non définies pour les types de données à l'exception de char. Quand j'ai pris un char non initialisé j'ai eu une valeur par défaut de zéro due à la représentation de piège. Ceci pourrait être utile pour toute utilisation de necv850es

1
répondu hanish 2013-05-30 10:29:53

étant donné que les ordinateurs ont une capacité de stockage limitée, les variables automatiques sont généralement conservées dans des éléments de stockage (qu'il s'agisse de registres ou de mémoire vive) qui ont déjà été utilisés à d'autres fins arbitraires. Si une telle variable est utilisée avant qu'une valeur lui ait été assignée, ce stockage peut contenir tout ce qu'elle détenait auparavant, et donc le contenu de la variable sera imprévisible.

comme une ride supplémentaire, de nombreux compilateurs peuvent conserver des variables dans les registres qui sont plus grands que les types associés. Bien qu'un compilateur soit nécessaire pour s'assurer que toute valeur qui est écrite à une variable et relue en arrière sera tronquée et/ou sign-étendu à sa taille appropriée, beaucoup de compilateurs effectueront une telle troncature lorsque les variables sont écrites et s'attendent à ce qu'il aura été effectué avant que la variable est lue. Sur de tels compilateurs, quelque chose comme:

uint16_t hey(uint32_t x, uint32_t mode)
{ uint16_t q; 
  if (mode==1) q=2; 
  if (mode==3) q=4; 
  return q; }

 uint32_t wow(uint32_t mode) {
   return hey(1234567, mode);
 }

pourrait très bien conduire à wow() stocker les valeurs 1234567 dans les registres 0 et 1, respectivement, et appelant foo() . Depuis x n'est pas nécessaire dans "foo", et puisque les fonctions sont censées mettre leur valeur de retour dans registre 0, le compilateur peut attribuer le registre 0 à q . Si mode est 1 ou 3, inscrire 0 sera chargé avec 2 ou 4, respectivement, mais si c'est une autre valeur, la fonction peut retourner ce qui était dans le registre 0 (i.e. valeur 1234567), même si cette valeur ne se situe pas dans la uint16_t.

pour éviter d'exiger des compilateurs de faire un travail supplémentaire pour s'assurer que non initialisés les variables ne semblent jamais contenir des valeurs en dehors de leur domaine, et éviter d'avoir besoin pour spécifier des comportements indéterminés dans les détails excessifs, la norme dit que l'utilisation de variables automatiques non initialisées est un comportement non défini. Dans certains cas, les conséquences peuvent être encore plus surprenant qu'une valeur étant en dehors de la gamme de son type. Par exemple, donné:

void moo(int mode)
{
  if (mode < 5)
    launch_nukes();
  hey(0, mode);      
}

un compilateur pourrait déduire que parce qu'invoquer moo() avec un mode qui est plus de 3 conduiront inévitablement au programme invoquant Non défini Comportement, le compilateur peut omettre n'importe quel code qui serait seulement pertinent si mode est égal ou supérieur à 4, tel que le code qui empêcherait normalement le lancement de bombes nucléaires dans de tels cas. Notez que ni la Norme, ni la philosophie de compilateur moderne, se soucierait du fait que la valeur de retour de "hey" est ignoré--l'acte d'essayer de le retourner donne un compilateur licence illimitée pour générer du code arbitraire.

1
répondu supercat 2016-05-06 15:28:36

la valeur de num sera une valeur de poubelle de la mémoire principale(RAM). son meilleur si vous initialisez la variable juste après la création.

-1
répondu Shrikant Singh 2014-05-25 11:57:22

autant que j'étais allé, il est principalement tributaire du compilateur, mais en général la plupart des cas, la valeur est pré assumée comme 0 par les compilateurs.

J'ai obtenu la valeur "garbage" dans le cas de VC++ tandis que TC a donné la valeur 0. Je L'imprime comme ci-dessous

int i;
printf('%d',i);
-3
répondu Rajeev Kumar 2012-06-27 19:34:39