Saya sudah lama berpikir bahwa dalam C, semua variabel harus dideklarasikan pada awal fungsi. Saya tahu bahwa di C99, aturannya sama seperti di C ++, tapi apa aturan penempatan deklarasi variabel untuk C89 / ANSI C?
Kode berikut berhasil dikompilasi dengan gcc -std=c89
dan gcc -ansi
:
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
char c = (i % 95) + 32;
printf("%i: %c\n", i, c);
char *s;
s = "some string";
puts(s);
}
return 0;
}
Tidakkah seharusnya deklarasi c
dan s
menyebabkan kesalahan dalam mode C89 / ANSI?
c
declaration
c89
mcjabberz
sumber
sumber
Jawaban:
Ini berhasil dikompilasi karena GCC memungkinkan deklarasi
s
sebagai ekstensi GNU, meskipun itu bukan bagian dari standar C89 atau ANSI. Jika Anda ingin mematuhi standar-standar itu dengan ketat, Anda harus melewati-pedantic
bendera.Deklarasi
c
pada awal{ }
blok adalah bagian dari standar C89; blok tidak harus berupa fungsi.sumber
s
ekstensi (dari sudut pandang C89). Deklarasic
legal sempurna di C89, tidak perlu ekstensi.Untuk C89, Anda harus mendeklarasikan semua variabel Anda di awal blok lingkup .
Jadi,
char c
deklarasi Anda valid karena berada di bagian atas blok lingkup loop. Tapi,char *s
deklarasi itu harus menjadi kesalahan.sumber
gcc
. Artinya, jangan percaya bahwa suatu program dapat dikompilasi untuk berarti bahwa itu sesuai.Pengelompokan deklarasi variabel di bagian atas blok adalah kemungkinan warisan karena keterbatasan kompiler C primitif yang lama. Semua bahasa modern merekomendasikan dan kadang-kadang bahkan menegakkan deklarasi variabel lokal pada titik terakhir: di mana mereka pertama kali diinisialisasi. Karena ini menghilangkan risiko menggunakan nilai acak secara tidak sengaja. Memisahkan deklarasi dan inisialisasi juga mencegah Anda menggunakan "const" (atau "final") ketika Anda bisa.
C ++ sayangnya terus menerima cara deklarasi lama dan teratas untuk kompatibilitas mundur dengan C (satu kompatibilitas C keluar dari yang lain ...) Tetapi C ++ mencoba untuk menjauh dari itu:
C99 mulai bergerak C ke arah yang sama ini.
Jika Anda khawatir tidak menemukan di mana variabel lokal dinyatakan maka itu berarti Anda memiliki masalah yang jauh lebih besar: blok penutup terlalu panjang dan harus dipecah.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimasikan+scope+of+variables+and+functions
sumber
int y; ... if (x) { printf("X was true"); y=23;} return y;
...null
, semua-bit-nol sering merupakan nilai perangkap yang berguna. Selanjutnya, dalam bahasa yang secara eksplisit menentukan bahwa variabel default ke semua-bit-nol, bergantung pada nilai itu bukan kesalahan . Kompiler belum cenderung terlalu aneh dengan "optimisasi" mereka, tetapi penulis kompiler terus berusaha untuk menjadi semakin pintar. Opsi kompiler untuk menginisialisasi variabel dengan variabel pseudo-acak yang disengaja mungkin berguna untuk mengidentifikasi kesalahan, tetapi hanya meninggalkan penyimpanan memegang nilai terakhirnya kadang-kadang dapat menutupi kesalahan.Dari sudut pandang rawatan, alih-alih sintaksis, setidaknya ada tiga jalur pemikiran:
Nyatakan semua variabel di awal fungsi sehingga mereka akan berada di satu tempat dan Anda akan dapat melihat daftar lengkap secara sekilas.
Nyatakan semua variabel sedekat mungkin ke tempat mereka pertama kali digunakan, jadi Anda akan tahu mengapa masing - masing diperlukan.
Deklarasikan semua variabel di awal blok lingkup terdalam, jadi mereka akan keluar dari cakupan sesegera mungkin dan memungkinkan kompiler untuk mengoptimalkan memori dan memberi tahu Anda jika Anda secara tidak sengaja menggunakannya di tempat yang tidak Anda inginkan.
Saya biasanya lebih suka opsi pertama, karena saya menemukan yang lain sering memaksa saya untuk mencari kode deklarasi. Mendefinisikan semua variabel di muka juga membuatnya lebih mudah untuk menginisialisasi dan melihatnya dari debugger.
Kadang-kadang saya akan mendeklarasikan variabel dalam blok lingkup yang lebih kecil, tetapi hanya untuk Alasan Bagus, yang saya miliki sangat sedikit. Salah satu contoh mungkin setelah a
fork()
, untuk mendeklarasikan variabel yang hanya dibutuhkan oleh proses anak. Bagi saya, indikator visual ini merupakan pengingat tentang tujuan mereka.sumber
Seperti dicatat oleh orang lain, GCC permisif dalam hal ini (dan mungkin kompiler lain, tergantung pada argumen yang mereka panggil) bahkan ketika dalam mode 'C89', kecuali jika Anda menggunakan pemeriksaan 'pedantic'. Sejujurnya, tidak ada banyak alasan bagus untuk tidak memakai obat; kode modern berkualitas harus selalu dikompilasi tanpa peringatan (atau sangat sedikit di mana Anda tahu Anda melakukan sesuatu yang spesifik yang mencurigakan ke kompiler sebagai kesalahan yang mungkin terjadi), jadi jika Anda tidak dapat membuat kode Anda dikompilasi dengan pengaturan yang bertele-tele mungkin perlu perhatian.
C89 mensyaratkan bahwa variabel harus dideklarasikan sebelum pernyataan lain dalam setiap lingkup, standar kemudian memungkinkan deklarasi lebih dekat untuk digunakan (yang dapat lebih intuitif dan lebih efisien), terutama deklarasi simultan dan inisialisasi variabel kontrol loop dalam loop 'untuk'.
sumber
Seperti telah dicatat, ada dua aliran pemikiran tentang ini.
1) Nyatakan semuanya di atas fungsi karena tahun 1987.
2) Deklarasikan paling dekat dengan penggunaan pertama dan dalam ruang lingkup sekecil mungkin.
Jawaban saya untuk ini adalah DO KEDUA! Biarkan saya jelaskan:
Untuk fungsi yang panjang, 1) membuat refactoring sangat sulit. Jika Anda bekerja dalam basis kode di mana pengembang menentang gagasan subrutin, maka Anda akan memiliki 50 deklarasi variabel di awal fungsi dan beberapa dari mereka mungkin hanya menjadi "i" untuk for-loop yang sangat bagian bawah fungsi.
Karena itu saya mengembangkan deklarasi-at-the-top-PTSD dari ini dan mencoba melakukan opsi 2) secara agama.
Saya kembali ke opsi satu karena satu hal: fungsi pendek. Jika fungsi Anda cukup pendek, maka Anda akan memiliki beberapa variabel lokal dan karena fungsi ini pendek, jika Anda menempatkannya di bagian atas fungsi, mereka akan tetap dekat dengan penggunaan pertama.
Juga, anti-pola "menyatakan dan mengatur ke NULL" ketika Anda ingin mendeklarasikan di atas tetapi Anda belum membuat beberapa perhitungan yang diperlukan untuk inisialisasi diselesaikan karena hal-hal yang perlu Anda inisialisasi kemungkinan akan diterima sebagai argumen.
Jadi sekarang pemikiran saya adalah bahwa Anda harus mendeklarasikan di bagian atas fungsi dan sedekat mungkin dengan penggunaan pertama. Jadi KEDUA! Dan cara untuk melakukannya adalah dengan subrutin yang dibagi dengan baik.
Tetapi jika Anda sedang mengerjakan fungsi yang panjang, maka letakkan hal-hal yang paling dekat dengan penggunaan pertama karena dengan cara itu akan lebih mudah untuk mengekstrak metode.
Resep saya adalah ini. Untuk semua variabel lokal, ambil variabel dan pindahkan deklarasi ke bawah, kompilasi, lalu pindahkan deklarasi ke sesaat sebelum kesalahan kompilasi. Itu penggunaan pertama. Lakukan ini untuk semua variabel lokal.
Sekarang, tentukan blok lingkup yang dimulai sebelum deklarasi dan pindahkan ujungnya hingga program dikompilasi
Ini tidak dikompilasi karena ada beberapa kode lagi yang menggunakan foo. Kita dapat melihat bahwa kompiler dapat melalui kode yang menggunakan bilah karena tidak menggunakan foo. Pada titik ini, ada dua pilihan. Yang mekanis adalah dengan hanya memindahkan "}" ke bawah hingga kompilasi, dan pilihan lainnya adalah memeriksa kode dan menentukan apakah urutannya dapat diubah menjadi:
Jika pesanan dapat diaktifkan, itu mungkin yang Anda inginkan karena mempersingkat masa pakai nilai sementara.
Hal lain yang perlu diperhatikan, apakah nilai foo perlu dipertahankan antara blok kode yang menggunakannya, atau mungkinkah itu hanya foo yang berbeda di keduanya. Sebagai contoh
Situasi ini membutuhkan lebih dari prosedur saya. Pengembang harus menganalisis kode untuk menentukan apa yang harus dilakukan.
Tetapi langkah pertama adalah menemukan penggunaan pertama. Anda dapat melakukannya secara visual tetapi kadang-kadang, itu hanya lebih mudah untuk menghapus deklarasi, mencoba untuk mengkompilasi dan hanya meletakkannya kembali di atas penggunaan pertama. Jika penggunaan pertama itu di dalam pernyataan if, letakkan di sana dan periksa apakah kompilasi. Kompiler kemudian akan mengidentifikasi kegunaan lain. Cobalah untuk membuat blok lingkup yang mencakup kedua kegunaan.
Setelah bagian mekanik ini dilakukan, maka menjadi lebih mudah untuk menganalisis di mana data berada. Jika variabel digunakan dalam blok lingkup besar, analisis situasi dan lihat apakah Anda hanya menggunakan variabel yang sama untuk dua hal yang berbeda (seperti "i" yang digunakan untuk dua untuk loop). Jika penggunaannya tidak terkait, buat variabel baru untuk masing-masing penggunaan yang tidak terkait ini.
sumber
Anda harus mendeklarasikan semua variabel di bagian atas atau "secara lokal" dalam fungsi. Jawabannya adalah:
Tergantung pada jenis sistem yang Anda gunakan:
1 / Sistem Tertanam (terutama yang terkait dengan kehidupan seperti Airplane atau Car): Ini memungkinkan Anda untuk menggunakan memori dinamis (misalnya: calloc, malloc, baru ...). Bayangkan Anda bekerja di proyek yang sangat besar, dengan 1000 insinyur. Bagaimana jika mereka mengalokasikan memori dinamis baru dan lupa menghapusnya (ketika tidak digunakan lagi)? Jika sistem tertanam berjalan untuk waktu yang lama, itu akan menyebabkan stack overflow dan perangkat lunak akan rusak. Tidak mudah memastikan kualitasnya (cara terbaik adalah melarang memori dinamis).
Jika sebuah Pesawat terbang dalam 30 hari dan tidak mematikan, apa yang terjadi jika perangkat lunak rusak (ketika pesawat masih di udara)?
2 / Sistem lain seperti web, PC (memiliki ruang memori besar):
Anda harus mendeklarasikan variabel "lokal" untuk mengoptimalkan penggunaan memori. Jika sistem ini berjalan untuk waktu yang lama dan terjadi stack overflow (karena seseorang lupa untuk menghapus memori dinamis). Lakukan saja hal sederhana untuk mengatur ulang PC: P Tidak berdampak pada nyawa
sumber
malloc()
. Meskipun saya belum pernah melihat perangkat yang tidak mampu melakukannya, adalah praktik terbaik untuk menghindari alokasi dinamis pada sistem tertanam ( lihat di sini ). Tapi itu tidak ada hubungannya dengan di mana Anda mendeklarasikan variabel Anda dalam suatu fungsi.sub rsp, 1008
mengalokasikan ruang untuk seluruh array di luar pernyataan if. Ini berlaku untukclang
dangcc
pada setiap versi dan level pengoptimalan yang saya coba.Saya akan mengutip beberapa pernyataan dari manual untuk versi gcc 4.7.0 untuk penjelasan yang jelas.
"Kompilator dapat menerima beberapa standar dasar, seperti 'c90' atau 'c ++ 98', dan dialek GNU dari standar-standar itu, seperti 'gnu90' atau 'gnu ++ 98'. Dengan menentukan standar dasar, kompiler akan menerima semua program yang mengikuti standar itu dan yang menggunakan ekstensi GNU yang tidak bertentangan dengannya. Misalnya, '-std = c90' mematikan fitur GCC tertentu yang tidak kompatibel dengan ISO C90, seperti asm dan jenis kata kunci, tetapi tidak ekstensi GNU lain yang tidak memiliki arti dalam ISO C90, seperti menghilangkan istilah tengah ekspresi?: "
Saya pikir titik kunci dari pertanyaan Anda adalah mengapa tidak gcc sesuai dengan C89 bahkan jika opsi "-std = c89" digunakan. Saya tidak tahu versi gcc Anda, tetapi saya pikir tidak akan ada perbedaan besar. Pengembang gcc telah memberi tahu kami bahwa opsi "-std = c89" hanya berarti ekstensi yang bertentangan dengan C89 dimatikan. Jadi, itu tidak ada hubungannya dengan beberapa ekstensi yang tidak memiliki arti di C89. Dan ekstensi yang tidak membatasi penempatan deklarasi variabel milik ekstensi yang tidak bertentangan dengan C89.
Sejujurnya, semua orang akan berpikir bahwa itu harus sesuai C89 sepenuhnya pada pandangan pertama dari opsi "-std = c89". Tapi ternyata tidak. Adapun masalah yang menyatakan semua variabel di awal lebih baik atau lebih buruk hanyalah masalah kebiasaan.
sumber