Apa yang terjadi jika saya mendefinisikan array ukuran 0 di C / C ++?

127

Hanya ingin tahu, apa yang sebenarnya terjadi jika saya mendefinisikan array nol-panjang int array[0];dalam kode? GCC tidak mengeluh sama sekali.

Program Sampel

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

Klarifikasi

Saya benar-benar mencoba mencari tahu apakah array panjang nol diinisialisasi dengan cara ini, bukannya diarahkan seperti panjang variabel dalam komentar Darhazer, dioptimalkan atau tidak.

Ini karena saya harus merilis beberapa kode keluar ke alam, jadi saya mencoba mencari tahu apakah saya harus menangani kasus-kasus di mana SIZEdidefinisikan sebagai 0, yang terjadi dalam beberapa kode dengan yang didefinisikan secara statisint array[SIZE];

Saya sebenarnya terkejut bahwa GCC tidak mengeluh, yang menyebabkan pertanyaan saya. Dari jawaban yang saya terima, saya percaya kurangnya peringatan sebagian besar karena mendukung kode lama yang belum diperbarui dengan sintaks [] baru.

Karena saya terutama bertanya-tanya tentang kesalahan, saya menandai jawaban Lundin sebagai benar (Nawaz pertama, tetapi tidak lengkap) - yang lain menunjukkan penggunaannya yang sebenarnya untuk struktur berlapis ekor, sementara yang relevan, bukan t persis apa yang saya cari.

Alex Koay
sumber
51
@AlexanderCorwin: Sayangnya di C ++, dengan perilaku tidak terdefinisi, ekstensi non-standar, dan anomali lainnya, mencoba sesuatu sendiri seringkali bukan jalan menuju pengetahuan.
Benjamin Lindley
5
@ JustinKirk Saya baru saja terjebak oleh itu juga dengan menguji dan melihat itu berhasil. Dan karena kritik yang saya terima di pos saya, saya belajar bahwa menguji dan melihatnya berfungsi tidak berarti valid dan legal. Jadi terkadang tes mandiri tidak valid.
StormByte
2
@JustinKirk, lihat jawaban Matthieu untuk contoh di mana Anda akan menggunakannya. Mungkin juga berguna dalam templat di mana ukuran array adalah parameter templat. Contoh dalam pertanyaan jelas di luar konteks.
Mark Ransom
2
@JustinKirk: Apa tujuan dari []Python atau bahkan ""dalam C? Terkadang, Anda memiliki fungsi atau makro yang membutuhkan array, tetapi Anda tidak memiliki data untuk dimasukkan ke dalamnya.
dan04
15
Apa itu "C / C ++"? Ini adalah dua bahasa terpisah
Lightness Races in Orbit

Jawaban:

86

Array tidak boleh memiliki ukuran nol.

ISO 9899: 2011 6.7.6.2:

Jika ekspresi adalah ekspresi konstan, itu harus memiliki nilai lebih besar dari nol.

Teks di atas benar untuk array biasa (paragraf 1). Untuk VLA (array panjang variabel), perilaku tidak terdefinisi jika nilai ekspresi kurang dari atau sama dengan nol (paragraf 5). Ini adalah teks normatif dalam standar C. Kompiler tidak diizinkan untuk mengimplementasikannya secara berbeda.

gcc -std=c99 -pedantic memberikan peringatan untuk kasus non-VLA.

Lundin
sumber
34
"ia harus benar-benar memberikan kesalahan" - perbedaan antara "peringatan" dan "kesalahan" tidak diakui dalam standar (hanya menyebutkan "diagnostik"), dan satu-satunya situasi di mana kompilasi harus berhenti [yaitu perbedaan dunia nyata antara peringatan dan kesalahan] ada pada pertemuan #errorarahan.
Random832
12
FYI, sebagai aturan umum, standar (C atau C ++) hanya menyatakan apa yang harus diizinkan oleh kompiler , tetapi bukan apa yang harus dilarangnya . Dalam beberapa kasus, mereka akan menyatakan bahwa kompiler harus mengeluarkan "diagnostik" tapi itu spesifik seperti yang mereka dapatkan. Sisanya diserahkan kepada vendor compiler. EDIT: Apa yang Random832 katakan juga.
mcmcc
8
@Lundin "Kompiler tidak diizinkan untuk membangun biner yang berisi array panjang nol." Standar mengatakan sama sekali tidak semacam itu. Ia hanya mengatakan bahwa ia harus menghasilkan setidaknya satu pesan diagnostik ketika diberi kode sumber yang berisi array dengan ekspresi konstan panjang nol untuk ukurannya. Satu-satunya keadaan di mana standar melarang kompiler untuk membangun biner adalah jika memenuhi #errorarahan preprosesor.
Acak 832
5
@Lundin Membuat biner untuk semua kasus yang benar memenuhi # 1, dan menghasilkan atau tidak menghasilkan satu untuk kasus yang salah tidak akan mempengaruhinya. Mencetak peringatan sudah cukup untuk # 3. Perilaku ini tidak memiliki relevansi dengan # 2, karena standar tidak mendefinisikan perilaku kode sumber ini.
Random832
13
@Lundin: Intinya pernyataan Anda salah; compiler sesuai yang diizinkan untuk membangun biner yang mengandung nol-panjang array, selama diagnostik dikeluarkan.
Keith Thompson
85

Sesuai standar, itu tidak diperbolehkan.

Namun sudah menjadi praktik saat ini dalam kompiler C untuk memperlakukan deklarasi tersebut sebagai deklarasi anggota array fleksibel ( FAM ) :

C99 6.7.2.1, §16 : Sebagai kasus khusus, elemen terakhir dari suatu struktur dengan lebih dari satu anggota bernama dapat memiliki tipe array yang tidak lengkap; ini disebut anggota array yang fleksibel.

Sintaks standar FAM adalah:

struct Array {
  size_t size;
  int content[];
};

Idenya adalah Anda akan mengalokasikannya demikian:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

Anda mungkin juga menggunakannya secara statis (ekstensi gcc):

Array a = { 3, { 1, 2, 3 } };

Ini juga dikenal sebagai struktur berlapis-empuk (istilah ini ada sebelum penerbitan Standar C99) atau struct hack (terima kasih kepada Joe Wreschnig karena menunjukkannya).

Namun sintaks ini distandarisasi (dan efeknya dijamin) hanya belakangan ini di C99. Sebelum ukuran konstan diperlukan.

  • 1 adalah cara portabel untuk pergi, meskipun agak aneh.
  • 0 lebih baik dalam menunjukkan niat, tetapi tidak sah sejauh Standar terkait dan didukung sebagai perpanjangan oleh beberapa penyusun (termasuk gcc).

Praktik padding tail, bagaimanapun, bergantung pada kenyataan bahwa penyimpanan tersedia (hati-hati malloc) sehingga tidak cocok untuk menumpuk penggunaan secara umum.

Matthieu M.
sumber
@Lundin: Saya belum melihat ada VLA di sini, semua ukuran diketahui pada waktu kompilasi. The Array fleksibel jangka berasal dari gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html dan doe memenuhi syarat int content[];di sini sejauh yang saya mengerti. Karena saya tidak terlalu paham tentang persyaratan C dari seni ... bisakah Anda mengkonfirmasi apakah alasan saya tampaknya benar?
Matthieu M.
@ MatthieuM .: C99 6.7.2.1, §16: Sebagai kasus khusus, elemen terakhir dari suatu struktur dengan lebih dari satu anggota bernama mungkin memiliki tipe array yang tidak lengkap; ini disebut anggota array yang fleksibel.
Christoph
Idiom ini juga dikenal dengan nama "struct hack" , dan saya telah bertemu lebih banyak orang yang akrab dengan nama itu daripada "tail-padded structure" (tidak pernah mendengarnya sebelumnya kecuali mungkin sebagai referensi umum untuk melapisi sebuah struct untuk kompatibilitas ABI di masa depan. ) atau "anggota array fleksibel" yang pertama kali saya dengar di C99.
1
Menggunakan ukuran array 1 untuk struct hack akan menghindari kompilasi squawk, tetapi hanya "portabel" karena penulis kompiler cukup baik untuk mengakui penggunaan seperti standar de-facto. Jika bukan karena larangan pada array berukuran nol, konsekuensinya penggunaan programmer dari elemen-tunggal array sebagai pengganti payah, dan sikap historis penulis kompiler bahwa mereka harus melayani kebutuhan programmer bahkan ketika tidak diharuskan oleh Standar, penulis kompiler dapat telah dengan mudah dan bermanfaat dioptimalkan foo[x]ke foo[0]setiap kali fooarray elemen tunggal.
supercat
1
@RobertSsupportsMonicaCellio: Seperti yang ditunjukkan secara eksplisit dalam jawabannya, tetapi pada akhirnya . Saya sudah memuat penjelasan di muka juga, untuk membuatnya lebih jelas dari awal.
Matthieu M.
58

Dalam Standar C dan C ++, array ukuran-nol tidak diperbolehkan ..

Jika Anda menggunakan GCC, kompilasi dengan -pedanticopsi. Ini akan memberi peringatan , mengatakan:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

Dalam kasus C ++, ia memberikan peringatan serupa.

Nawaz
sumber
9
Dalam Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Mark Ransom
4
-Werror hanya mengubah semua peringatan menjadi kesalahan, yang tidak memperbaiki perilaku yang salah dari kompiler GCC.
Lundin
C ++ Builder 2009 juga memberikan kesalahan dengan benar:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin
1
Alih-alih -pedantic -Werror, Anda juga bisa melakukannya-pedantic-errors
Stephan Dollberg
3
Array berukuran nol tidak cukup sama dengan array berukuran nol std::array. (Selain: Saya ingat tetapi tidak dapat menemukan sumber yang dipertimbangkan dan secara eksplisit ditolak VLA dari berada di C ++.)
27

Benar-benar ilegal, dan selalu demikian, tetapi banyak penyusun mengabaikan sinyal kesalahan. Saya tidak yakin mengapa Anda ingin melakukan ini. Satu penggunaan yang saya tahu adalah untuk memicu kesalahan waktu kompilasi dari boolean:

char someCondition[ condition ];

Jika conditionsalah, maka saya mendapatkan kesalahan waktu kompilasi. Karena kompiler mengizinkan ini, saya menggunakan:

char someCondition[ 2 * condition - 1 ];

Ini memberikan ukuran 1 atau -1, dan saya tidak pernah menemukan kompiler yang akan menerima ukuran -1.

James Kanze
sumber
Ini adalah retasan yang menarik untuk digunakan.
Alex Koay
10
Itu trik umum dalam metaprogramming, saya pikir. Saya tidak akan terkejut jika implementasi STATIC_ASSERTmenggunakannya.
James Kanze
Mengapa tidak adil:#if condition \n #error whatever \n #endif
Jerfov2
1
@ Jerfov2 karena kondisinya mungkin tidak diketahui pada waktu preprocessing, hanya waktu kompilasi
rmeador
9

Saya akan menambahkan bahwa ada seluruh halaman dokumentasi online gcc pada argumen ini.

Beberapa kutipan:

Array tanpa panjang diizinkan dalam GNU C.

Di ISO C90, Anda harus memberi konten panjang 1

dan

Versi GCC sebelum 3.0 memungkinkan array nol-panjang untuk diinisialisasi secara statis, seolah-olah mereka array fleksibel. Selain kasus-kasus yang berguna, itu juga memungkinkan inisialisasi dalam situasi yang akan merusak data selanjutnya

jadi kamu bisa

int arr[0] = { 1 };

dan booming :-)

xanatos
sumber
Dapat saya lakukan seperti int a[0], maka a[0] = 1 a[1] = 2??
Suraj Jain
2
@ SurajJain Jika Anda ingin menimpa tumpukan Anda :-) C tidak memeriksa indeks vs ukuran array yang Anda tulis, jadi Anda bisa a[100000] = 5tetapi jika Anda beruntung Anda hanya akan crash aplikasi Anda, jika Anda beruntung: -)
xanatos
Int a [0]; berarti array variabel (array berukuran nol), Bagaimana Saya Bisa Sekarang menugaskannya
Suraj Jain
@ SurajJain Bagian mana dari "C tidak memeriksa indeks vs ukuran array yang Anda tulis" tidak jelas? Tidak ada indeks memeriksa dalam C, Anda dapat menulis setelah akhir array dan crash komputer atau menimpa bit berharga dari memori Anda. Jadi jika Anda memiliki array 0 elemen, Anda bisa menulis setelah akhir 0 elemen.
xanatos
Lihat ini quora.com/...
Suraj Jain
9

Penggunaan lain dari array panjang-nol adalah untuk membuat objek panjang variabel (pra-C99). Zero-panjang array yang berbeda dari array yang fleksibel yang memiliki [] tanpa 0.

Dikutip dari gcc doc :

Array panjang nol diizinkan di GNU C. Mereka sangat berguna sebagai elemen terakhir dari struktur yang benar-benar header untuk objek panjang variabel:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

Dalam ISO C99, Anda akan menggunakan anggota array fleksibel, yang sedikit berbeda dalam sintaks dan semantik:

  • Anggota array fleksibel ditulis sebagai konten [] tanpa 0.
  • Anggota array fleksibel memiliki tipe yang tidak lengkap, sehingga ukuran operator mungkin tidak diterapkan.

Contoh dunia nyata adalah array panjang nol struct kdbus_itemdi kdbus.h (modul kernel Linux).

Bangsawan tinggi
sumber
2
IMHO, tidak ada alasan yang baik untuk Standar melarang array nol panjang; bisa saja benda berukuran nol baik-baik saja sebagai anggota struktur dan menganggapnya sebagai void*tujuan aritmatika (sehingga menambah atau mengurangi pointer ke benda berukuran nol akan dilarang). Sementara Fleksibel Anggota Array sebagian besar lebih baik daripada array berukuran nol, mereka juga dapat bertindak sebagai semacam "persatuan" untuk alias hal-hal tanpa menambahkan tingkat tambahan "sindiran" tipuan untuk apa yang mengikuti (misalnya diberikan struct foo {unsigned char as_bytes[0]; int x,y; float z;}seseorang dapat mengakses anggota x.. z...
supercat
... langsung tanpa harus mengatakan misalnya myStruct.asFoo.x, dll. Lebih lanjut, IIRC, C squawks pada setiap upaya untuk memasukkan anggota array yang fleksibel dalam sebuah struct, sehingga membuatnya tidak mungkin memiliki struktur yang mencakup beberapa anggota array fleksibel lainnya dengan panjang yang diketahui. kandungan.
supercat
@supercat alasan yang baik adalah untuk menjaga integritas aturan tentang mengakses batas array luar. Sebagai anggota terakhir dari sebuah struct, anggota array fleksibel C99 mencapai efek yang persis sama dengan array berukuran nol GCC, tetapi tanpa perlu menambahkan case khusus ke aturan lain. IMHO ini merupakan peningkatan yang sizeof x->contentsmerupakan kesalahan dalam ISO C sebagai lawan mengembalikan 0 dalam gcc. Array berukuran nol yang bukan anggota struct memperkenalkan banyak masalah lain.
MM
@ MM: Masalah apa yang akan mereka sebabkan jika mengurangi dua pointer sama ke objek berukuran nol didefinisikan sebagai menghasilkan nol (seperti akan mengurangi pointer sama dengan ukuran objek), dan mengurangi pointer yang tidak sama dengan objek berukuran nol didefinisikan sebagai menghasilkan Nilai Tidak Ditentukan? Jika Standar telah menetapkan bahwa suatu implementasi dapat memungkinkan sebuah struct yang mengandung FAM untuk disematkan di dalam struct lain asalkan elemen berikutnya dalam struct yang kedua adalah array dengan tipe elemen yang sama dengan FAM atau struct yang dimulai dengan array seperti itu. , dan asalkan ...
supercat
... itu mengakui FAM sebagai aliasing array (jika aturan penyelarasan akan menyebabkan array mendarat di offset yang berbeda, diagnostik akan diperlukan), itu akan sangat berguna. Karena itu, tidak ada cara yang baik untuk memiliki metode yang menerima pointer ke struktur format umum struct {int n; THING dat[];}dan dapat bekerja dengan hal-hal durasi statis atau otomatis.
supercat
6

Deklarasi array ukuran-nol dalam struct akan berguna jika diizinkan, dan jika semantiknya sedemikian sehingga (1) mereka akan memaksa penyelarasan tetapi jika tidak mengalokasikan ruang apa pun, dan (2) mengindeks array akan dianggap perilaku yang didefinisikan dalam kasus di mana pointer yang dihasilkan akan berada dalam blok memori yang sama dengan struct. Perilaku seperti itu tidak pernah diizinkan oleh standar C apa pun, tetapi beberapa kompiler lama mengizinkannya sebelum menjadi standar bagi kompiler untuk memungkinkan deklarasi array tidak lengkap dengan kurung kosong.

Struct hack, seperti yang biasa diimplementasikan menggunakan array ukuran 1, adalah cerdik dan saya tidak berpikir ada persyaratan yang kompiler menahan diri dari melanggar itu. Sebagai contoh, saya akan berharap bahwa jika seorang kompiler melihat int a[1], itu akan menjadi haknya untuk menganggapnya a[i]sebagai a[0]. Jika seseorang mencoba mengatasi masalah penyelarasan struct hack melalui sesuatu seperti

typedef struct {
  ukuran uint32_t;
  data uint8_t [4]; // Gunakan empat, untuk menghindari agar padding tidak membuang ukuran struct
}

kompiler mungkin menjadi pintar dan menganggap ukuran array benar-benar empat:

; Seperti yang tertulis
  foo = myStruct-> data [i];
; Seperti yang ditafsirkan (dengan asumsi perangkat keras little-endian)
  foo = ((* (uint32_t *) myStruct-> data) >> (i << 3)) & 0xFF;

Optimalisasi semacam itu mungkin masuk akal, terutama jika myStruct->datadapat dimuat ke dalam register dalam operasi yang sama dengan myStruct->size. Saya tidak tahu apa-apa dalam standar yang akan melarang optimasi seperti itu, meskipun tentu saja itu akan merusak kode apa pun yang mungkin berharap untuk mengakses barang-barang di luar elemen keempat.

supercat
sumber
1
Anggota array fleksibel ditambahkan ke C99 sebagai versi yang sah dari hack struct
MM
Standar memang mengatakan bahwa akses ke anggota array yang berbeda tidak bertentangan, yang cenderung membuat optimasi itu tidak mungkin.
Ben Voigt
@ BenVoigt: Standar bahasa C tidak menentukan efek penulisan byte dan membaca kata yang mengandung secara bersamaan, tetapi 99,9% dari prosesor menentukan bahwa penulisan akan berhasil dan kata itu akan berisi versi baru atau lama dari byte bersama dengan konten yang tidak diubah dari byte lainnya. Jika kompiler menargetkan prosesor tersebut, apa yang akan menjadi konflik?
supercat
@supercat: Standar bahasa C menjamin bahwa penulisan simultan ke dua elemen array yang berbeda tidak akan konflik. Jadi argumen Anda yang (baca saat menulis) berfungsi dengan baik, tidak cukup.
Ben Voigt
@ BenVoigt: Jika sepotong kode misalnya menulis ke elemen array 0, 1, dan 2 dalam beberapa urutan, itu tidak akan diizinkan untuk membaca keempat elemen menjadi panjang, memodifikasi tiga, dan menulis kembali keempat, tetapi saya pikir itu akan diizinkan untuk membaca keempat menjadi panjang, memodifikasi tiga, menulis kembali 16 bit lebih pendek, dan bit 16-23 sebagai byte. Apakah Anda tidak setuju dengan itu? Dan kode yang hanya perlu membaca elemen array akan diizinkan untuk membacanya menjadi panjang dan menggunakannya.
supercat