2012-11-21 11 views
12
int main() 
{ 
    char a[7] = "Network"; 
    return 0; 
} 

ciąg dosłowne w C jest zakończony wewnętrznie z nul charakteru. Tak więc powyższy kod powinien dać błąd kompilacji, ponieważ rzeczywista długość literału literowego Network wynosi 8 i nie może się zmieścić w tablicy char[7].Dlaczego gcc zezwala na inicjalizację tablic znakowych z literałem łańcuchowym większym niż tablica?

Jednak, gcc (nawet z -Wall) na Ubuntu kompiluje ten kod bez żadnego błędu lub ostrzeżenia. Dlaczego gcc pozwala na to, a nie oznaczać go jako błąd kompilacji?

gcc daje tylko ostrzeżenie (nadal nie ma błędu!), Gdy rozmiar tablicy char jest mniejszy niż ciąg dosłownym. Na przykład, ostrzega na:

char a[6] = "Network"; 

[Pokrewne] Visual C++ 2012 daje błąd kompilacji dla char a[7]:

1>d:\main.cpp(3): error C2117: 'a' : array bounds overflow 
1> d:\main.cpp(3) : see declaration of 'a' 
+6

Być może kompilujesz kod jako C z gcc i jako C++ w visual studio. Są tylko pozornie podobnymi językami. – nos

+0

@nos: Tak, to wyjaśnia zachowanie! Sądzę, że jest to jedna z tych drobnych różnic w C-podobnym kodzie między C i C++. –

+1

To będzie błąd z g ++, jeśli dobrze pamiętam. – fayyazkl

Odpowiedz

21

Inicjowanie tablicy char z ciągiem dosłownym, który jest większy niż jest dobrze w C, ale źle w C++. To wyjaśnia różnicę w zachowaniu między gcc i VC++.

Nie można uzyskać błędu, jeśli skompilowano taki sam plik C z VC++. I dostaniesz błąd, jeśli skompilujesz go jako plik C++ z g ++.

Standard C mówi:

Tablica typu znak może być zainicjowany przez ciąg znaków dosłownym lub UTF-8 Łańcuch znaków, ewentualnie w nawiasach. Kolejne bajty napisu dosłownym (w tym kończąca zerowej charakteru, jeśli nie ma miejsca lub jeśli tablica jest o nieznanej wielkości) zainicjować elementów tablicy.

[...]

Przykład 8

Zgłoszenie są inicjowane

char s[] = "abc", t[3] = "abc"; 

definiuje "zwykły '' Tablica znak obiektów s i t której elementy z ciągiem znaków literały. Ta deklaracja jest identyczna

char s[] = { 'a', 'b', 'c', '\0' }, 
    t[] = { 'a', 'b', 'c' }; 

(sekcja 6.7.9 części C11 draft standard rzeczywista sformułowanie ostatecznej normy może być inny).

Oznacza to, że jest to całkowicie poprawne spadać zakończenie znak, jeśli tablica nie ma miejsca na to. To może być nieoczekiwane, ale dokładnie tak powinien działać język i (przynajmniej dla mnie) dobrze znana funkcja.

Na Przeciwnie, C++ norma mówi:

Nie powinno być więcej niż inicjalizatory istnieją elementy tablicy.

przykład:

char cv[4] = "asdf"; // error 

jest słabo formowane ponieważ nie ma miejsca dla zrozumienia końcowe '\ 0'.

(. 8.5.2 z C++ 2011 Projekt n3242)

+1

Unwind: Jeśli jest to dozwolone przez standard, każdy pomysł, dlaczego VC++ oznacza to jako błąd? –

+0

Odwijać: Jeśli znak null został usunięty z literału (w powyższej inicjalizacji), czy można bezpiecznie używać takiego łańcucha w funkcjach takich jak strlen? –

+1

@Ashwin Nie jesteś pewien Visual Studio, może kompiluje się jako C++, dla którego reguły * mogą * być inne? Nie, to nie jest bezpieczne, ponieważ 'strlen()' wymaga prawidłowego łańcucha zakończonego znakiem 0. – unwind

3

Preferowany sposób deklarowania ciąg dosłowny jest zwykle:

char a[] = "Network"; 
    printf("size of a: %d\n", sizeof a); // The compiler 'knows' the size of a. 
    // this prints '8' 

Niech postać kompilator go. Uciążliwe jest ręczne określanie rozmiaru tablicy i utrzymywanie jej w synchronizacji z rzeczywistą długością literału ciągu znaków ...

Domyślam się, że GCC tak naprawdę nie zajmuje się czymś więcej niż ostrzeżeniem.

+3

-1: Nie odpowiedziałeś na pytanie. –

3

We wczesnych dniach C i Unix, pamięć i dysk były małe, więc nie przechowywanie bajtu NUL na końcu łańcucha było właściwie techniką, która została użyta. Jeśli zmienna łańcuchowa ma siedem znaków, możesz zapisać w niej siedmioznakowy ciąg znaków, a ponieważ siedem jest długością maksymalną, wiesz, że ciąg zakończył się tam, nawet bez znaku kończącego. To dlatego strncpy działa tak, jak robi.

0

Podczas gdy unwind's answer wyjaśnia, dlaczego gcc nie ostrzega o tym, nie mówi, co możesz z tym zrobić.

gcc „s -Wc++-compat opcja ostrzeżenie wykryje tego konkretnego problemu z komunikatem:

foo.c: In function ‘main’: 
foo.c:3:17: warning: initializer-string for array chars is too long for C++ [-Wc++-compat] 

to jedyna opcja, która powoduje gcc ostrzec o tym problemie. Możesz napisać krótki skrypt, aby szybko przywołać opcje ostrzegawcze na stronie man z gcc, spróbuj skompilować je z każdym i sprawdź, czy narzeka.

$ time for F in $(man gcc | grep -o -- '-W[^= ]*') 
    do if gcc -c "${F}" foo.c |& grep :3 >& /dev/null; then 
     echo "${F}"; gcc -c "${F}" foo.c 
    fi 
    done 
man gcc | grep -o -- '-W[^= ]*') 
man gcc | grep -o -- '-W[^= ]*' 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wc++-compat 
foo.c: In function ‘main’: 
foo.c:3:17: warning: initializer-string for array chars is too long for C++ [-Wc++-compat] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused-variable 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wtraditional 
foo.c: In function ‘main’: 
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused-variable 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused-variable 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wunused 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wtraditional 
foo.c: In function ‘main’: 
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional] 
-Wtraditional 
foo.c: In function ‘main’: 
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional] 
-Wc++-compat 
foo.c: In function ‘main’: 
foo.c:3:17: warning: initializer-string for array chars is too long for C++ [-Wc++-compat] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wtraditional 
foo.c: In function ‘main’: 
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wall 
foo.c: In function ‘main’: 
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable] 
-Wtraditional 
foo.c: In function ‘main’: 
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional] 

real 0m26.399s 
user 0m5.128s 
sys 0m15.329s 

Ogólnie lint -jak narzędzie takie jak splint poinformuje o różnego rodzaju potencjalnych problemów. W tym przypadku będzie napisane:

foo.c:3:17: String literal with 8 characters is assigned to char [7] (no room 
       for null terminator): "Network" 
    A string literal is assigned to a char array that is not big enough to hold 
    the null terminator. (Use -stringliteralnoroom to inhibit warning) 
foo.c:3:10: Variable a declared but not used 
Powiązane problemy