Konsep kata kunci statis dari perspektif tertanam C

9
static volatile unsigned char   PORTB   @ 0x06;

Ini adalah baris kode dalam file header mikrokontroler PIC. The @operator yang digunakan untuk menyimpan nilai PORTB dalam alamat 0x06, yang merupakan mendaftar di dalam PIC controller yang mewakili PORTB. Sampai saat ini, saya punya ide yang jelas.

Baris ini dinyatakan sebagai variabel global di dalam file header ( .h). Jadi, dari apa yang saya ketahui tentang bahasa C, "variabel global statis" tidak dapat dilihat oleh file lain - atau, sederhananya, variabel / fungsi global statis tidak dapat digunakan di luar file saat ini.

Lalu, bagaimana kata kunci ini PORTBdapat dilihat oleh file sumber utama saya dan banyak file header lainnya yang saya buat secara manual?

Pada file sumber utama saya, saya hanya menambahkan file header #include pic.hApakah ini ada hubungannya dengan pertanyaan saya?

Electro Voyager
sumber
2
tidak ada masalah dengan pertanyaan tetapi bagian SE yang salah aku takut
gommer
statis biasanya digunakan di dalam fungsi untuk menentukan bahwa variabel dibuat sekali dan menjaga nilainya dari satu eksekusi fungsi ke yang berikutnya. variabel global adalah variabel yang dibuat di luar fungsi apa pun sehingga terlihat di mana-mana. statis global tidak terlalu masuk akal.
Finbarr
8
@Finbarr Salah staticglobal terlihat di dalam seluruh unit kompilasi tunggal, dan tidak diekspor lebih dari itu. Mereka seperti privateanggota kelas di OOP. Yaitu setiap variabel yang perlu dibagi antara fungsi yang berbeda di dalam unit kompilasi tetapi tidak seharusnya terlihat di luar yang seharusnya benar-benar terjadi static. Ini juga mengurangi "clobbering" dari namespace global program.
JimmyB
2
Re "Operator @ digunakan untuk menyimpan nilai PORTB di dalam alamat 0x06" . Betulkah? Bukankah lebih seperti "Operator @ digunakan untuk menyimpan variabel" PORTB "di alamat memori absolut 0x06" ?
Peter Mortensen

Jawaban:

20

Kata kunci 'statis' dalam C memiliki dua arti yang berbeda secara fundamental.

Lingkup Pembatas

Dalam konteks ini, 'statis' berpasangan dengan 'extern' untuk mengontrol ruang lingkup variabel atau nama fungsi. Statis menyebabkan variabel atau nama fungsi hanya tersedia dalam satu unit kompilasi dan hanya tersedia untuk kode yang ada setelah deklarasi / definisi dalam teks unit kompilasi.

Batasan ini sendiri sebenarnya hanya berarti sesuatu jika dan hanya jika Anda memiliki lebih dari satu unit kompilasi dalam proyek Anda. Jika Anda hanya memiliki satu unit kompilasi, maka ia masih melakukan hal-hal tetapi efeknya sebagian besar tidak ada gunanya (kecuali jika Anda suka menggali file objek untuk membaca apa yang dihasilkan kompiler.)

Seperti disebutkan, kata kunci ini dalam konteks ini berpasangan dengan kata kunci 'extern', yang melakukan sebaliknya - dengan membuat variabel atau nama fungsi ditautkan dengan nama yang sama yang ditemukan di unit kompilasi lainnya. Jadi, Anda dapat melihat 'statis' yang membutuhkan variabel atau nama untuk ditemukan dalam unit kompilasi saat ini, sementara 'eksternal' memungkinkan hubungan unit kompilasi silang.

Seumur Hidup Statis

Seumur hidup statis berarti bahwa variabel ada sepanjang durasi program (betapapun lama itu.) Ketika Anda menggunakan 'statis' untuk mendeklarasikan / mendefinisikan variabel dalam suatu fungsi, itu berarti bahwa variabel dibuat beberapa saat sebelum penggunaan pertama ( yang berarti, setiap kali saya mengalaminya, bahwa variabel dibuat sebelum main () dimulai) dan tidak dihancurkan setelahnya. Bahkan ketika eksekusi fungsi selesai dan kembali ke pemanggilnya. Dan seperti halnya variabel seumur hidup statis yang dinyatakan di luar fungsi, mereka diinisialisasi pada saat yang sama - sebelum main () mulai - ke nol semantik (jika tidak ada inisialisasi disediakan) atau ke nilai eksplisit yang ditentukan, jika diberikan.

Ini berbeda dari variabel fungsi tipe 'otomatis', yang dibuat baru (atau, seolah-olah baru) setiap kali fungsi dimasukkan dan kemudian dihancurkan (atau, seolah-olah dihancurkan) ketika fungsi keluar.

Berbeda dengan dampak penerapan 'statis' pada definisi variabel di luar fungsi, yang berdampak langsung pada ruang lingkupnya, menyatakan variabel fungsi (dalam suatu fungsi tubuh, jelas) sebagai 'statis' tidak memiliki dampak pada cakupannya. Lingkup ditentukan oleh fakta bahwa itu didefinisikan dalam suatu fungsi tubuh. Variabel seumur hidup statis yang didefinisikan dalam fungsi memiliki cakupan yang sama dengan variabel 'otomatis' lainnya yang didefinisikan dalam badan fungsi - ruang lingkup fungsi.

Ringkasan

Jadi kata kunci 'statis' memiliki konteks yang berbeda dengan jumlah yang berarti "makna yang sangat berbeda." Alasan itu digunakan dalam dua cara, seperti ini, adalah untuk menghindari penggunaan kata kunci lain. (Ada diskusi panjang tentang hal itu.) Ia merasa bahwa programmer dapat mentolerir penggunaan dan nilai menghindari kata kunci lain dalam bahasa lebih penting (daripada argumen sebaliknya.)

(Semua variabel yang dinyatakan di luar fungsi memiliki masa pakai statis dan tidak perlu kata kunci 'statis' untuk menjadikannya benar. Jadi, jenis kata kunci yang dibebaskan ini yang akan digunakan di sana berarti sesuatu yang sama sekali berbeda: 'hanya dapat dilihat dalam satu kompilasi) unit. "Ini semacam retas, semacamnya.)

Catatan Khusus

volatile char unsigned static PORTB @ 0x06;

Kata 'statis' di sini harus diartikan berarti bahwa penghubung tidak akan berusaha mencocokkan beberapa kejadian PORTB yang dapat ditemukan di lebih dari satu unit kompilasi (dengan asumsi kode Anda memiliki lebih dari satu.)

Ini menggunakan sintaks khusus (non-portabel) untuk menentukan "lokasi" (atau nilai numerik label yang biasanya merupakan alamat) dari PORTB. Jadi linker diberikan alamatnya dan tidak perlu menemukannya untuk itu. Jika Anda memiliki dua unit kompilasi yang menggunakan baris ini, mereka akhirnya akan menunjuk ke tempat yang sama. Jadi tidak perlu memberi label 'eksternal', di sini.

Seandainya mereka menggunakan 'eksternal' mungkin menimbulkan masalah. Linker kemudian dapat melihat (dan akan mencoba untuk mencocokkan) beberapa referensi ke PORTB yang ditemukan dalam beberapa kompilasi. Jika semuanya menentukan alamat seperti ini, dan alamatnya TIDAK sama untuk beberapa alasan [kesalahan?], Lalu apa yang harus dilakukan? Mengeluh? Atau? (Secara teknis, dengan 'extern' aturan praktisnya adalah bahwa hanya SATU unit kompilasi yang akan menentukan nilainya dan yang lain tidak.)

Hanya saja lebih mudah untuk menandainya sebagai 'statis', menghindari membuat linker khawatir tentang konflik, dan hanya menyalahkan kesalahan apa pun untuk alamat yang tidak cocok pada siapa pun yang mengubah alamat menjadi sesuatu yang tidak seharusnya.

Bagaimanapun, variabel diperlakukan sebagai 'masa hidup statis'. (Dan 'mudah berubah'.)

Sebuah deklarasi bukanlah definisi , tetapi semua definisi deklarasi

Dalam C, definisi membuat objek. Itu juga mendeklarasikannya. Tetapi deklarasi biasanya tidak (lihat catatan di bawah) membuat objek.

Berikut ini adalah definisi dan deklarasi:

static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }

Berikut ini bukan definisi, tetapi hanya deklarasi:

extern int b;
extern int f();

Perhatikan bahwa deklarasi tidak membuat objek aktual. Mereka hanya menyatakan rincian tentang hal itu, yang kemudian dapat digunakan oleh kompiler untuk membantu menghasilkan kode yang benar dan untuk memberikan pesan peringatan dan kesalahan, yang sesuai.

  • Di atas, saya katakan "biasanya," dengan penuh pertimbangan. Dalam beberapa kasus, deklarasi dapat membuat objek dan karena itu dipromosikan ke definisi oleh linker (tidak pernah oleh kompiler.) Jadi, bahkan dalam kasus yang jarang terjadi ini, kompiler C masih berpikir bahwa deklarasi hanyalah deklarasi. Ini adalah fase tautan yang membuat promosi yang diperlukan dari beberapa pernyataan. Ingatlah ini dengan cermat.

    Dalam contoh di atas, jika ternyata hanya ada deklarasi untuk "extern int b;" di semua unit kompilasi tertaut, maka tautan tersebut dibebankan tanggung jawab untuk membuat definisi. Ketahuilah bahwa ini adalah acara tautan waktu. Compiler sama sekali tidak sadar, selama kompilasi. Ini hanya dapat ditentukan pada waktu tautan, jika suatu deklarasi jenis ini paling dipromosikan.

    Kompiler sadar bahwa "static int a;" tidak dapat dipromosikan oleh tautan pada waktu tautan, jadi ini sebenarnya adalah definisi pada waktu kompilasi .

jonk
sumber
3
Jawaban bagus, +1! Hanya satu poin kecil: Mereka bisa menggunakan extern, dan itu akan menjadi cara C yang lebih tepat untuk melakukannya: Mendeklarasikan variabel externdalam file header untuk dimasukkan beberapa kali dalam program dan mendefinisikannya dalam beberapa file non-header untuk dikompilasi dan terhubung tepat sekali. Setelah semua, PORTB yang seharusnya menjadi persis salah satu contoh dari variabel yang berbeda cu dapat merujuk. Jadi penggunaan di staticsini adalah jenis jalan pintas yang mereka ambil untuk menghindari kebutuhan file .c selain file header.
JimmyB
Saya juga akan mencatat bahwa variabel statis yang dideklarasikan dalam suatu fungsi tidak diubah di seluruh fungsi panggilan yang dapat berguna untuk fungsi-fungsi yang perlu mempertahankan semacam informasi negara (saya telah menggunakannya secara khusus untuk keperluan ini di masa lalu).
Peter Smith
@ Peter saya pikir saya mengatakan itu. Tapi mungkin tidak sebaik yang Anda inginkan?
Jonk
@ JimmyB Tidak, mereka tidak bisa menggunakan 'extern', sebagai gantinya, untuk deklarasi variabel fungsi yang berperilaku seperti 'statis'. 'extern' sudah menjadi opsi untuk deklarasi variabel (bukan definisi) di dalam badan fungsi dan melayani tujuan yang berbeda - menyediakan akses waktu tautan ke variabel yang ditentukan di luar fungsi apa pun. Tapi mungkin saja saya salah paham maksud Anda juga.
Jonk
1
@JimmyB Extern linkage pasti akan mungkin, meskipun saya tidak tahu apakah itu "lebih tepat." Satu pertimbangan adalah bahwa kompiler mungkin dapat memancarkan kode yang lebih optimal jika informasi ditemukan di unit terjemahan. Untuk skenario tertanam, menyimpan siklus pada setiap pernyataan IO bisa menjadi masalah besar.
Cort Ammon
9

statics tidak terlihat di luar unit kompilasi saat ini , atau "unit terjemahan". Ini tidak sama dengan file yang sama .

Perhatikan bahwa Anda memasukkan file header ke file sumber apa pun di mana Anda mungkin memerlukan variabel yang dideklarasikan dalam header. Inklusi ini menjadikan file header bagian dari unit terjemahan saat ini dan (contoh dari) variabel terlihat di dalamnya.

JimmyB
sumber
Terima kasih untuk balasan Anda. "Unit kompilasi", Maaf saya tidak mengerti, bisakah Anda menjelaskan istilah itu. Izinkan saya mengajukan satu pertanyaan lagi, bahkan jika kami ingin menggunakan variabel dan fungsi yang ditulis di dalam file lain, kami harus TERMASUK file itu ke FILE SUMBER utama kami. Lalu mengapa kata kunci "static volatile" di file header itu.
Electro Voyager
1
Diskusi yang cukup mendalam di stackoverflow.com/questions/572547/what-does-static-mean-in-c
Peter Smith
3
@ElectroVoyager; jika Anda menyertakan header yang sama yang berisi deklarasi statis dalam beberapa file sumber c, maka masing-masing file tersebut akan memiliki variabel statis dengan nama yang sama, tetapi mereka bukan variabel yang sama .
Peter Smith
2
Dari tautan @JimmyB: Files included by using the #include preprocessor directive become part of the compilation unit.Saat Anda memasukkan file header (.h) dalam file .c, anggap itu memasukkan konten header di file sumber, dan sekarang, ini adalah unit kompilasi Anda. Jika Anda mendeklarasikan variabel statis atau fungsi dalam file .c, Anda dapat menggunakannya hanya dalam file yang sama, yang pada akhirnya, akan menjadi unit kompilasi lain.
gustavovelascoh
5

Saya akan mencoba merangkum komentar dan jawaban @ JimmyB dengan contoh penjelasan:

Misalkan kumpulan file ini:

static_test.c:

#include <stdio.h>
#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one();

void main(){

    say_hello();
    printf("var is %d\n", var);
    var_add_one();
    printf("now var is %d\n", var);
}

static.h:

static int var=64;
static void say_hello(){
    printf("Hello!!!\n");
};

no_static.h:

int var=64;
void say_hello(){
    printf("Hello!!!\n");
};

static_src.c:

#include <stdio.h>

#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one(){
    var = var + 1;
    printf("Added 1 to var: %d\n", var);
    say_hello();
}

Anda dapat mengkompilasi dan menjalankan kode menggunakan gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_testuntuk menggunakan header statis atau gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_testuntuk menggunakan header non-statis.

Perhatikan bahwa dua unit kompilasi hadir di sini: static_src dan static_test. Ketika Anda menggunakan versi statis header ( -DUSE_STATIC=1), versi vardan say_helloakan tersedia untuk setiap unit kompilasi, ini, kedua unit dapat menggunakannya, tetapi periksa bahwa meskipun var_add_one()bertahap fungsi yang var variabel, ketika fungsi utama mencetak nya var variabel , masih 64:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test                                                                                                                       14:33:12
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 64

Sekarang, jika Anda mencoba untuk mengkompilasi dan menjalankan kode, menggunakan versi non-statis ( -DUSE_STATIC=0), itu akan menimbulkan kesalahan penautan karena definisi variabel digandakan:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test                                                                                                                       14:35:30
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test

Semoga ini bisa membantu Anda mengklarifikasi masalah ini.

gustavovelascoh
sumber
4

#include pic.hkira-kira berarti "menyalin isi pic.h ke file saat ini". Akibatnya, setiap file yang termasuk pic.hmendapatkan definisi lokalnya sendiri PORTB.

Mungkin Anda bertanya-tanya mengapa tidak ada definisi global tunggal PORTB. Alasannya cukup sederhana: Anda hanya dapat mendefinisikan sebuah variabel global dalam satu file C, jadi jika Anda ingin menggunakan PORTBdalam beberapa file dalam proyek Anda, Anda akan perlu pic.hdengan deklarasi dari PORTBdan pic.cdengan nya definisi . Membiarkan setiap file C menentukan salinannya sendiri PORTBmembuat pembuatan kode lebih mudah, karena Anda tidak harus memasukkan file proyek yang tidak Anda tulis.

Manfaat tambahan variabel statis vs global adalah Anda mendapatkan lebih sedikit konflik penamaan. File AC yang tidak menggunakan fitur perangkat keras MCU (dan dengan demikian tidak termasuk pic.h) dapat menggunakan nama PORTBuntuk tujuannya sendiri. Bukannya itu ide yang baik untuk melakukannya dengan sengaja, tetapi ketika Anda mengembangkan misalnya perpustakaan matematika agnostik MCU, Anda akan terkejut betapa mudahnya untuk secara tidak sengaja menggunakan kembali nama yang digunakan oleh salah satu MCU di luar sana.

Dmitry Grigoryev
sumber
"Anda akan terkejut betapa mudahnya untuk secara tidak sengaja menggunakan kembali nama yang digunakan oleh salah satu MCU di luar sana" - Saya berani berharap bahwa semua perpustakaan matematika hanya menggunakan nama huruf kecil, dan bahwa semua lingkungan MCU hanya menggunakan huruf besar untuk mendaftar nama.
vsz
@vsz LAPACK untuk satu penuh dengan nama all-caps historis.
Dmitry Grigoryev
3

Sudah ada beberapa jawaban yang baik, tetapi saya pikir penyebab kebingungan perlu ditangani secara sederhana dan langsung:

Deklarasi PORTB bukan standar C. Ini adalah perpanjangan dari bahasa pemrograman C yang hanya bekerja dengan kompiler PIC. Perlu ekstensi karena PICs tidak dirancang untuk mendukung C.

Penggunaan statickata kunci di sini membingungkan karena Anda tidak akan pernah menggunakan staticcara itu dalam kode normal. Untuk variabel global, Anda akan menggunakan externdi header, bukan static. Tetapi PORTB bukan variabel normal . Ini adalah retasan yang memberi tahu kompiler untuk menggunakan instruksi perakitan khusus untuk mendaftar IO. Mendeklarasikan PORTB staticmembantu mengelabui kompiler untuk melakukan hal yang benar.

Ketika digunakan pada lingkup file, staticmembatasi ruang lingkup variabel atau fungsi untuk file itu. "File" berarti file C dan apa pun yang disalin ke dalamnya oleh preprosesor. Saat Anda menggunakan #include, Anda menyalin kode ke file C. Karena itulah menggunakan staticheader tidak masuk akal - alih-alih satu variabel global, setiap file yang # termasuk header akan mendapatkan salinan variabel yang terpisah.

Bertentangan dengan kepercayaan populer, staticselalu berarti hal yang sama: alokasi statis dengan ruang lingkup terbatas. Inilah yang terjadi pada variabel sebelum dan sesudah diumumkan static:

+------------------------+-------------------+--------------------+
| Variable type/location |    Allocation     |       Scope        |
+------------------------+-------------------+--------------------+
| Normal in file         | static            | global             |
| Normal in function     | automatic (stack) | limited (function) |
| Static in file         | static            | limited (file)     |
| Static in function     | static            | limited (function) |
+------------------------+-------------------+--------------------+

Yang membuatnya membingungkan adalah bahwa perilaku default variabel tergantung pada di mana mereka didefinisikan.

Adam Haun
sumber
2

Alasan file utama dapat melihat definisi port "statis" adalah karena arahan #include. Arahan itu setara dengan memasukkan seluruh file header ke kode sumber Anda pada baris yang sama dengan arahan itu sendiri.

Kompiler microchip XC8 memperlakukan file .c dan .h persis sama sehingga Anda dapat menempatkan definisi variabel Anda di salah satu.

Biasanya file header berisi referensi "extern" ke variabel yang didefinisikan di tempat lain (biasanya file .c).

Variabel port perlu ditentukan pada alamat memori tertentu yang cocok dengan perangkat keras yang sebenarnya. Jadi definisi aktual (non eksternal) perlu ada di suatu tempat.

Saya hanya bisa menebak mengapa Microchip corporation memilih untuk memasukkan definisi aktual dalam file .h. Tebakan yang mungkin adalah bahwa mereka hanya menginginkan satu file (.h) alih-alih 2 (.h dan .c) (untuk mempermudah pengguna).

Tetapi jika Anda memasukkan definisi variabel aktual dalam file header dan kemudian memasukkan header itu ke beberapa file sumber maka linker akan mengeluh bahwa variabel didefinisikan beberapa kali.

Solusinya adalah mendeklarasikan variabel sebagai statis maka setiap definisi diperlakukan sebagai lokal ke file objek itu dan linker tidak akan mengeluh.

pengguna4574
sumber