web-dev-qa-db-fra.com

Pourquoi non initialisé au lieu de hors limites?

Dans le code ci-dessous, pourquoi b[9] non initialisé au lieu de hors limites?

#include <stdio.h>

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    printf("b[9] = %d\n", b[9]);

    return 0;
}

Appel du compilateur:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:6:5: warning: ‘b[9]’ is used uninitialized in this function [-Wuninitialized]
     printf("b[9] = %d\n", b[9]);
% gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.6) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Mise à jour: Maintenant, c'est étrange:

#include <stdio.h>

void foo(char *);

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    foo(&b[9]);
    foo(&b[10]);
    printf("b[9] = %d\n", b[9]);
    printf("b[10] = %d\n", b[10]);

    return 0;
}

Compiler cela donne les avertissements auxquels on peut s'attendre:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:9:5: warning: array subscript is above array bounds [-Warray-bounds]
     foo(&b[10]);
     ^
foo.c:10:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[9] = %d\n", b[9]);
                             ^
foo.c:11:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[10] = %d\n", b[10]);

Soudainement, gcc voit les limites pour ce que c'est.

45

Je pense que cela pourrait être le cas ici: dans le premier code, GCC remarque que vous n'avez pas du tout besoin du tableau char entier, juste b[9], Donc il peut remplacer le code par

char b_9; // = ???
printf("b[9] = %d\n", b_9);

Maintenant, c'est une transformation complètement légale, car comme le tableau a été accédé hors limites, le comportement est complètement indéfini. Ce n'est que dans cette dernière phase qu'il remarque alors que cette variable, qui remplace b[9], N'est pas initialisée et émet le message de diagnostic.

Pourquoi je crois ça? Parce que si j'ajoute juste tout code qui référencera l'adresse du tableau dans mémoire , par exemple printf("%p\n", &b[8]); n'importe où, le tableau est maintenant entièrement réalisé en mémoire, et le compilateur diagnostiquera l'indice du tableau est au-dessus des limites du tableau .


Ce que je trouve encore plus intéressant, c'est que GCC ne diagnostique pas du tout l'accès hors limites à moins que les optimisations ne soient activées. Cela suggérerait à nouveau que chaque fois que vous écrivez un nouveau programme, vous devriez le compiler avec des optimisations activées pour rendre les bogues très visibles au lieu de les garder cachés avec le mode débogage;)

57
Antti Haapala

Le comportement en lecture b[9] ou b[10] est non défini.

Votre compilateur émet un avertissement (il n'est pas obligé de le faire), bien que le texte d'avertissement soit un peu trompeur, mais pas techniquement incorrect. À mon avis, c'est plutôt intelligent. (Un compilateur C est pas requis pour émettre un diagnostic d'accès hors limites.)

En ce qui concerne &b[9], le compilateur est pas autorisé à déréférencer cela, et doit l'évaluer comme b + 9. Vous êtes autorisé à définir un pointeur au-delà de la fin d'un tableau. Le comportement de la définition d'un pointeur sur &b[10] est non défini.

17
Bathsheba

Quelques résultats expérimentaux supplémentaires.


En utilisant char b[9] au lieu de char b[] semble ne faire aucune différence, gcc avertit toujours la même chose avec char b[9].

Il est intéressant de noter que l'initialisation de l'élément à passe unique via le membre "suivant" dans un struct 1) supprime l'avertissement "non initialisé" et 2) ne met pas en garde contre l'accession en dehors du tableau.

#include <stdio.h>

typedef struct {
  char c[9];
  char d[9];
} TwoNines;

int main(void) {
  char b[9] = { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' };
  printf("b[] size %zu\n", sizeof b);
  printf("b[9] = %d\n", b[9]);   // 'b[9]' is used uninitialized in this function [-Wuninitialized]

  TwoNines e = { { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' }, //
                 { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' } };

  printf("e size %zu\n", sizeof e);
  printf("e.c[9] = %d\n", e.c[9]);   // No warning.

  return 0;
}

Sortie

b[] size 9
b[9] = 0
e size 18    // With 18, we know `e` is packed.
e.c[9] = 78  // 'N'

Remarques:
gcc -std = c11 -O3 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length = 0 -v -MMD -MP ...
gcc/gcc-7.3.0-2.i686

1
chux