Warum nicht initialisiert statt außerhalb der Grenzen?

Lesezeit: 6 Minuten

Benutzeravatar von Goswin von Brederlow
Goswin von Brederlow

Im folgenden Code steht warum b[9] nicht initialisiert statt out-of-bounds?

#include <stdio.h>

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

    return 0;
}

Compiler-Aufruf:

% 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.

Update: Das ist jetzt seltsam:

#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;
}

Das Kompilieren führt zu den Warnungen, die man erwarten würde:

% 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]);

Plötzlich sieht gcc das Out-of-Bounds als das, was es ist.

  • Interessanterweise klingeln bekommt es richtig.

    – Gaurav Sehgal

    17. Juli 2018 um 12:45 Uhr

  • Versuchen printf("b[10] = %d\n", b[10]); 9 ist eins nach dem Ende des Arrays und ist eine zulässige Adresse (obwohl sie noch nicht definiert ist, um sie tatsächlich zu dereferenzieren …).

    – Andreas Henle

    17. Juli 2018 um 12:50 Uhr

  • @AndrewHenle Aber b[9] ist eine Dereferenzierung, &b[9] wäre gültig. Etwas mehr Seltsamkeit fügte der Frage hinzu.

    – Goswin von Brederlow

    17. Juli 2018 um 13:02 Uhr

  • Eins nach dem Ende des Arrays kann anders behandelt werden – und in Ihrem ersten Fall nicht ganz richtig. Siehe die Absätze zur Zeigerarithmetik im C-Standard: https://port70.net/~nsz/c/c11/n1570.html#6.5.6p8

    – Andreas Henle

    17. Juli 2018 um 13:08 Uhr


  • Die unterschiedlichen Warnungen stammen wahrscheinlich von unterschiedlichen gcc-Versionen. Das Verhalten von beide Ihre Beispiele sind durch die Standards nicht definiert, Compiler sind es also nicht erforderlich irgendetwas Besonderes mit ihnen zu tun – Warnungen sind nicht erforderlich. Das Problem für einen Compiler-Entwickler besteht darin, dass sich undefiniertes Verhalten auf unbegrenzte Weise manifestieren kann. Daher ist es für einen Compiler schwierig, schnell (im Sinne von Programmierern, die nicht meckern, dass das Kompilieren zu lange dauert) herauszufinden, welche Warnung die “beste” ist.

    – Petrus

    17. Juli 2018 um 13:34 Uhr

Antti Haapala -- Benutzeravatar von Слава Україні
Antti Haapala – Слава Україні

Ich glaube, das könnte hier der Fall sein: Im ersten Code bemerkt GCC, dass Sie nicht das gesamte char-Array benötigen, sondern nur b[9]sodass der Code durch ersetzt werden kann

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

Nun, das ist ein vollständig legale Transformation, denn da auf das Array außerhalb der Grenzen zugegriffen wurde, ist das Verhalten völlig undefiniert. Erst in der späteren Phase merkt er dann, dass diese Variable, die ein Ersatz für ist b[9]ist nicht initialisiert und gibt die Diagnosemeldung aus.

Warum ich das glaube? Denn wenn ich hinzufüge nur jeder Code, der wird Hinweis die Adresse des Arrays in Erinnerungzum Beispiel printf("%p\n", &b[8]); Überall ist das Array jetzt vollständig im Speicher realisiert und der Compiler wird es diagnostizieren Array-Index liegt oberhalb der Array-Grenzen.


Was ich noch interessanter finde, ist, dass GCC den Zugriff außerhalb der Grenzen überhaupt nicht diagnostiziert, es sei denn, Optimierungen sind aktiviert. Dies würde wiederum darauf hindeuten, dass Sie jedes Mal, wenn Sie ein neues Programm schreiben, es mit aktivierten Optimierungen kompilieren sollten, um die Fehler gut sichtbar zu machen, anstatt sie im Debug-Modus zu verbergen;)

  • Ich stimme zu. gcc -O0-Code ist langsam, unlesbar, überspringt viele Warnungen und hat wenig Relevanz für alles, was Sie versenden möchten. Sie sollten immer -Os oder -O2 verwenden.

    – Goswin von Brederlow

    6. August 2018 um 16:14 Uhr

Benutzeravatar von Bathsheba
Bathseba

Das Verhalten beim Lesen b[9] oder b[10] ist nicht definiert.

Ihr Compiler gibt eine Warnung aus (muss nicht), obwohl der Warntext etwas irreführend, aber technisch nicht falsch ist. Meiner Meinung nach ziemlich clever. (AC-Compiler ist nicht erforderlich, um eine Diagnose für den Zugriff außerhalb der Grenzen auszustellen.)

Bezüglich &b[9]der Compiler ist nicht darf das dereferenzieren und muss es als auswerten b + 9. Sie dürfen einen Zeiger eins nach dem Ende eines Arrays setzen. Das Verhalten beim Setzen eines Zeigers auf &b[10] ist nicht definiert.

  • Offensichtlich. Die Frage bezieht sich speziell auf die Warnung, die gcc anzeigt. Nicht über das Verhalten oder die Gültigkeit des Codes. Gcc behandelt jetzt Out-of-Bounds-Fehler und die Frage ist, warum es hier nicht verwendet wird. Weitere Seltsamkeiten finden Sie im Update.

    – Goswin von Brederlow

    17. Juli 2018 um 13:05 Uhr


  • @GoswinvonBrederlow: Die gcc-Warnung ist korrekt. b[9] ist nicht initialisiert.

    – Bathseba

    17. Juli 2018 um 13:06 Uhr

  • Es ist wirklich nicht uninitialisiert. Es ist außerhalb der Grenzen. es zu dereferenzieren ist einfach falsch.

    – Goswin von Brederlow

    17. Juli 2018 um 13:08 Uhr

  • @GoswinvonBrederlow: Wir können uns darauf einigen, in diesem Punkt anderer Meinung zu sein. Ihre Frage ist jetzt interessanter, nachdem Sie &b hinzugefügt haben[9] &c.

    – Bathseba

    17. Juli 2018 um 13:09 Uhr

  • WAHR b[9] ist nicht initialisiert. Es ist auch außerhalb der b[] Reihe. Ich habe versucht zu “initialisieren” b[9] und haben immer noch nur 9 Elemente. Ergebnis unterstützt “Warntext ist etwas irreführend, aber technisch nicht falsch.”

    – chux – Wiedereinsetzung von Monica

    17. Juli 2018 um 14:42 Uhr


Einige zusätzliche experimentelle Ergebnisse.


Verwenden char b[9] Anstatt von char b[] scheint keinen unterschied zu machen, gcc warnt trotzdem gleich mit char b[9].

Interessanterweise initialisiert man das One-Passed-Element über das „next“-Member in a struct 1) stillt die “nicht initialisierte” Warnung und 2) warnt nicht vor dem Zugriff außerhalb des Arrays.

#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;
}

Ausgabe

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

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

  • Bezüglich des Zugriffs auf das “nächste” Mitglied: Das ist zulässig, weil: 1. Ein Zeiger auf das erste Mitglied einem Zeiger auf die gesamte Struktur entspricht, 2. Jedes Objekt sicher in ein Char-Array umgewandelt und dereferenziert werden kann, um die Byte-Darstellung zu überprüfen. 3. e.c ist zufälligerweise ein char-Array, also 4. Sie geben die gesamte Struktur in ein char-Array ein.

    – Kevin

    18. Juli 2018 um 4:46 Uhr

Benutzeravatar von 0___________
0___________

Wenn Sie den Code mit -O2 kompilieren, wird diese Variable aufgrund der Trivialität des Beispiels optimiert. Die Warnung ist also zu 100% richtig

1405210cookie-checkWarum nicht initialisiert statt außerhalb der Grenzen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy