Apa arti 'const static' di C dan C ++?

117
const static int foo = 42;

Saya melihat ini di beberapa kode di sini di StackOverflow dan saya tidak tahu apa fungsinya. Kemudian saya melihat beberapa jawaban yang membingungkan di forum lain. Tebakan terbaik saya adalah bahwa ini digunakan di C untuk menyembunyikan konstanta foodari modul lain. Apakah ini benar? Jika demikian, mengapa ada orang yang menggunakannya dalam konteks C ++ di mana Anda bisa membuatnya private?

c0m4
sumber

Jawaban:

113

Ini memiliki kegunaan di C dan C ++.

Seperti yang Anda duga, staticbagian tersebut membatasi cakupannya pada unit kompilasi tersebut . Ini juga menyediakan inisialisasi statis. consthanya memberitahu kompilator untuk tidak membiarkan siapa pun memodifikasinya. Variabel ini diletakkan di segmen data atau bss tergantung pada arsitekturnya, dan mungkin berada dalam memori bertanda hanya-baca.

Semua itu adalah bagaimana C memperlakukan variabel ini (atau bagaimana C ++ memperlakukan variabel namespace). Di C ++, anggota yang ditandai staticdibagikan oleh semua instance kelas tertentu. Entah itu privat atau tidak, tidak memengaruhi fakta bahwa satu variabel digunakan bersama oleh banyak contoh. Setelah constdi sana akan memperingatkan Anda jika ada kode yang mencoba memodifikasi itu.

Jika itu benar-benar pribadi, maka setiap instance kelas akan mendapatkan versinya sendiri (terlepas dari pengoptimal).

Chris Arguin
sumber
1
Contoh aslinya berbicara tentang "variabel pribadi". Oleh karena itu, ini adalah mebmer dan statis tidak mempengaruhi hubungan. Anda harus menghapus "bagian statis membatasi cakupannya ke file itu".
Richard Corden
"Bagian khusus" dikenal sebagai segmen data, yang dibagikan dengan semua variabel global lainnya, seperti "string" eksplisit dan larik global. Ini berlawanan dengan segmen kode.
spoulson
@Richard - apa yang membuat Anda berpikir itu adalah anggota kelas? Tidak ada dalam pertanyaan yang mengatakan demikian. Jika itu adalah anggota kelas, maka Anda benar, tetapi jika itu hanya variabel yang dideklarasikan dalam lingkup global, maka Chris benar.
Graeme Perrow
1
Poster asli menyebutkan pribadi sebagai solusi yang mungkin lebih baik, tetapi bukan sebagai masalah aslinya.
Chris Arguin
@ Graeme, OK jadi ini bukan anggota "pasti" - namun, jawaban ini membuat pernyataan yang hanya berlaku untuk anggota namespace dan pernyataan itu salah untuk variabel anggota. Mengingat jumlah suara yang kesalahan dapat membingungkan seseorang yang tidak terlalu paham dengan bahasanya - itu harus diperbaiki.
Richard Corden
212

Banyak orang memberikan jawaban dasar tetapi tidak ada yang menunjukkan bahwa dalam C ++ constdefault staticpada namespacetingkat (dan beberapa memberikan informasi yang salah). Lihat standar C ++ 98 bagian 3.5.3.

Pertama beberapa latar belakang:

Unit terjemahan: File sumber setelah pra-prosesor (secara rekursif) menyertakan semua file yang disertakan.

Kaitan statis: Simbol hanya tersedia di dalam unit terjemahannya.

Keterkaitan eksternal: Simbol tersedia dari unit terjemahan lain.

Di namespacelevel

Ini termasuk namespace global alias variabel global .

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

Di tingkat fungsi

staticberarti nilainya dipertahankan di antara panggilan fungsi.
Semantik staticvariabel fungsi mirip dengan variabel global karena berada di segmen data program (dan bukan tumpukan atau tumpukan), lihat pertanyaan ini untuk detail selengkapnya tentang staticmasa pakai variabel.

Di classlevel

staticartinya nilai tersebut dibagi antara semua instance kelas dan constartinya tidak berubah.

Motti
sumber
2
Pada tingkat fungsi: statis tidak reduntan dengan const, mereka dapat berperilaku berbeda const int *foo(int x) {const int b=x;return &b};dibandingkanconst int *foo(int x) {static const int b=x;return &b};
Hanczar
1
Pertanyaannya adalah tentang C dan C ++ jadi Anda harus menyertakan catatan tentang consthanya menyiratkan staticdi C ++ .
Nikolai Ruhe
@Motti: Jawaban bagus. Bisakah Anda menjelaskan apa yang membuat apa yang menjadi mubazir pada tingkat fungsi? Apakah Anda mengatakan bahwa constdeklarasi juga mengandung arti static? Seperti, jika Anda membuang const, dan mengubah nilainya, semua nilai akan diubah?
Cookie
1
@Motti consttidak menyiratkan statis pada tingkat fungsi, itu akan menjadi mimpi buruk konkurensi (konst! = Ekspresi konstan), semua yang ada di tingkat fungsi secara implisit auto. Karena pertanyaan ini juga diberi tag [c], saya harus menyebutkan bahwa tingkat global const intsecara implisit ada externdi C. Namun, aturan yang Anda miliki di sini menjelaskan C ++ dengan sempurna.
Ryan Haining
1
Dan di C ++, dalam ketiga kasus, staticmenunjukkan bahwa variabel adalah durasi statis (hanya ada satu salinan, yang berlangsung dari awal program hingga akhir), dan memiliki tautan internal / statis jika tidak ditentukan lain (ini diganti oleh fungsi linkage untuk variabel statis lokal, atau linkage kelas untuk anggota statis). Perbedaan utama terletak pada apa yang tersirat dalam setiap situasi staticyang valid.
Waktu Justin - Kembalikan Monica
45

Baris kode tersebut sebenarnya dapat muncul dalam beberapa konteks berbeda dan meskipun perilakunya kira-kira sama, ada perbedaan kecil.

Cakupan Namespace

// foo.h
static const int i = 0;

' i' akan terlihat di setiap unit terjemahan yang menyertakan tajuk. Namun, kecuali Anda benar-benar menggunakan alamat objek (misalnya. ' &i'), Saya cukup yakin bahwa kompilator akan memperlakukan ' i' hanya sebagai tipe aman 0. Jika dua unit terjemahan lagi menggunakan ' &i' maka alamatnya akan berbeda untuk setiap unit terjemahan.

// foo.cc
static const int i = 0;

' i' memiliki hubungan internal, sehingga tidak dapat dirujuk dari luar unit terjemahan ini. Namun, sekali lagi kecuali Anda menggunakan alamatnya, kemungkinan besar akan diperlakukan sebagai tipe-aman 0.

Satu hal yang perlu diperhatikan, adalah pernyataan berikut:

const int i1 = 0;

adalah persis sama dengan static const int i = 0. Variabel dalam namespace yang dideklarasikan dengan constdan tidak secara eksplisit dideklarasikan dengan externstatis secara implisit. Jika Anda memikirkan hal ini, komite C ++ bermaksud untuk mengizinkan constvariabel dideklarasikan dalam file header tanpa selalu memerlukan statickata kunci untuk menghindari kerusakan ODR.

Ruang Lingkup Kelas

class A {
public:
  static const int i = 0;
};

Dalam contoh di atas, standar secara eksplisit menetapkan bahwa ' i' tidak perlu ditentukan jika alamatnya tidak diperlukan. Dengan kata lain jika Anda hanya menggunakan ' i' sebagai tipe-aman 0 maka kompilator tidak akan mendefinisikannya. Satu perbedaan antara versi kelas dan namespace adalah bahwa alamat ' i' (jika digunakan dalam dua atau lebih unit terjemahan) akan sama untuk anggota kelas. Jika alamat digunakan, Anda harus memiliki definisinya:

// a.h
class A {
public:
  static const int i = 0;
};

// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address
Richard Corden
sumber
2
1 untuk menunjukkan bahwa konstanta statis sama dengan hanya konst pada ruang lingkup namespace.
Plumenator
Sebenarnya tidak ada perbedaan antara menempatkan di "foo.h" atau "foo.cc" karena .h hanya disertakan saat menyusun unit terjemahan.
Mikhail
2
@ Mikhail: Anda benar. Ada asumsi bahwa tajuk dapat disertakan dalam beberapa TU dan karenanya berguna untuk membicarakannya secara terpisah.
Richard Corden
24

Ini adalah pengoptimalan ruang kecil.

Saat Anda berkata

const int foo = 42;

Anda tidak mendefinisikan sebuah konstanta, tetapi membuat variabel hanya-baca. Kompilator cukup pintar untuk menggunakan 42 setiap kali ia melihat foo, tetapi ia juga akan mengalokasikan ruang di area data yang diinisialisasi untuknya. Ini dilakukan karena, sebagaimana didefinisikan, foo memiliki keterkaitan eksternal. Unit kompilasi lain dapat mengatakan:

extern const int foo;

Untuk mendapatkan akses ke nilainya. Itu bukan praktik yang baik karena unit kompilasi itu tidak tahu apa nilai foo. Itu hanya tahu itu adalah const int dan harus memuat ulang nilai dari memori setiap kali digunakan.

Sekarang, dengan menyatakan bahwa itu statis:

static const int foo = 42;

Kompilator dapat melakukan pengoptimalan seperti biasa, tetapi ia juga dapat berkata "hei, tidak ada orang di luar unit kompilasi ini yang dapat melihat foo dan saya tahu selalu 42 sehingga tidak perlu mengalokasikan ruang untuk itu."

Saya juga harus mencatat bahwa di C ++, cara yang disukai untuk mencegah nama keluar dari unit kompilasi saat ini adalah dengan menggunakan namespace anonim:

namespace {
    const int foo = 42; // same as static definition above
}
Ferruccio
sumber
1
, u disebutkan tanpa menggunakan statis "itu juga akan mengalokasikan ruang di area data yang diinisialisasi untuk itu". dan dengan menggunakan statis "tidak perlu mengalokasikan ruang untuk itu". (dari mana kompiler memilih val?) dapatkah Anda menjelaskan dalam istilah heap dan tumpukan di mana variabel disimpan. Koreksi saya jika saya menafsirkannya salah.
Nihar
@ N.Nihar - Area data statis adalah potongan memori berukuran tetap yang berisi semua data yang memiliki hubungan statis. Ini "dialokasikan" oleh proses memuat program ke dalam memori. Ini bukan bagian dari tumpukan atau heap.
Ferruccio
Apa yang terjadi jika saya memiliki fungsi yang mengembalikan pointer ke foo? Apakah itu merusak pengoptimalan?
nw.
@nw: Ya, itu harus.
Ferruccio
8

Ini kehilangan 'int'. Harus:

const static int foo = 42;

Di C dan C ++, ia mendeklarasikan konstanta integer dengan cakupan file lokal bernilai 42.

Mengapa 42? Jika Anda belum mengetahuinya (dan sulit dipercaya bahwa Anda belum mengetahuinya), ini merujuk pada Jawaban atas Kehidupan, Semesta, dan Segalanya .

Kevin
sumber
Terima kasih ... sekarang setiap ... selama sisa hidup saya ... ketika saya melihat 42, saya akan selalu memikirkan hal ini. haha
Inisheer
Ini adalah bukti positif bahwa alam semesta diciptakan dengan keberadaan 13 jari (pertanyaan dan jawaban sebenarnya cocok dengan basis 13).
paxdiablo
Ini tikusnya. 3 jari di setiap kaki, ditambah satu ekor memberi Anda basis 13.
KeithB
Anda tidak benar-benar membutuhkan 'int' dalam sebuah deklarasi, meskipun jelas merupakan selera yang bagus untuk menuliskannya. C selalu mengasumsikan tipe 'int' secara default. Cobalah!
ephemient
"dengan cakupan file lokal nilai 42" ?? atau untuk seluruh unit kompilasi?
aniliitb10
4

Di C ++,

static const int foo = 42;

adalah cara yang lebih disukai untuk mendefinisikan & menggunakan konstanta. Yaitu menggunakan ini daripada

#define foo 42

karena tidak merusak sistem keamanan tipe.

paxos1977
sumber
4

Untuk semua jawaban hebat, saya ingin menambahkan detail kecil:

Jika Anda menulis plugin (mis. DLL atau pustaka .so untuk dimuat oleh sistem CAD), maka statis adalah penyelamat yang menghindari benturan nama seperti ini:

  1. Sistem CAD memuat plugin A, yang memiliki "const int foo = 42;" di dalamnya.
  2. Sistem memuat plugin B, yang memiliki "const int foo = 23;" di dalamnya.
  3. Hasilnya, plugin B akan menggunakan nilai 42 untuk foo, karena plugin loader akan menyadari, bahwa sudah ada "foo" dengan external linkage.

Lebih buruk lagi: Langkah 3 mungkin berperilaku berbeda tergantung pada pengoptimalan kompiler, mekanisme pemuatan plugin, dll.

Saya mengalami masalah ini sekali dengan dua fungsi pembantu (nama yang sama, perilaku berbeda) di dua plugin. Mendeklarasikan mereka statis memecahkan masalah.

Hitam
sumber
Sesuatu tampak aneh tentang benturan nama antara dua plugin, yang membuat saya memeriksa peta tautan untuk salah satu dari banyak DLL saya yang mendefinisikan m_hDfltHeap sebagai pegangan dengan tautan eksternal. Benar saja, itu semua untuk dilihat dan digunakan di seluruh dunia, tercantum dalam peta tautan sebagai _m_hDfltHeap. Aku sudah lupa semua tentang factoid ini.
David A. Gray
4

Menurut spesifikasi C99 / GNU99:

  • static

    • adalah penentu kelas penyimpanan

    • objek lingkup tingkat file secara default memiliki hubungan eksternal

    • objek lingkup tingkat file dengan penentu statis memiliki hubungan internal
  • const

    • adalah type-qualifier (merupakan bagian dari type)

    • Kata kunci diterapkan ke contoh kiri langsung - yaitu

      • MyObj const * myVar; - penunjuk yang tidak memenuhi syarat ke tipe objek yang memenuhi syarat

      • MyObj * const myVar; - const pointer yang memenuhi syarat ke tipe objek yang tidak memenuhi syarat

    • Penggunaan paling kiri - diterapkan pada tipe objek, bukan variabel

      • const MyObj * myVar; - penunjuk yang tidak memenuhi syarat ke tipe objek yang memenuhi syarat

JADI:

static NSString * const myVar; - penunjuk konstan ke string yang tidak dapat diubah dengan hubungan internal.

Tidak adanya statickata kunci akan membuat nama variabel menjadi global dan dapat menyebabkan konflik nama dalam aplikasi.

Alexey Pelekh
sumber
4

inlineVariabel C ++ 17

Jika Anda mencari "C ++ const static" di Google, kemungkinan besar yang benar-benar ingin Anda gunakan adalah variabel inline C ++ 17 .

Fitur C ++ 17 yang luar biasa ini memungkinkan kita untuk:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Kompilasi dan jalankan:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream .

Lihat juga: Bagaimana cara kerja variabel sebaris?

Standar C ++ pada variabel sebaris

Standar C ++ menjamin bahwa alamatnya akan sama. C ++ 17 N4659 standard draft 10.1.6 "The inline specifier":

6 Fungsi atau variabel sebaris dengan hubungan eksternal harus memiliki alamat yang sama di semua unit terjemahan.

cppreference https://en.cppreference.com/w/cpp/language/inline menjelaskan bahwa jika statictidak diberikan, maka ia memiliki tautan eksternal.

Penerapan variabel sebaris GCC

Kita dapat mengamati bagaimana ini diterapkan dengan:

nm main.o notmain.o

yang mengandung:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

dan man nmmengatakan tentang u:

"u" Simbolnya adalah simbol global yang unik. Ini adalah ekstensi GNU untuk set standar binding simbol ELF. Untuk simbol seperti itu, dynamic linker akan memastikan bahwa dalam keseluruhan proses hanya ada satu simbol dengan nama dan tipe ini yang digunakan.

jadi kami melihat bahwa ada ekstensi ELF khusus untuk ini.

Pra-C ++ 17: extern const

Sebelum C ++ 17, dan di C, kita dapat mencapai efek yang sangat mirip dengan sebuah extern const, yang akan mengarah ke satu lokasi memori yang digunakan.

Kerugiannya inlineadalah:

  • tidak mungkin membuat variabel constexprdengan teknik ini, hanya inlinememungkinkan: Bagaimana cara mendeklarasikan constexpr extern?
  • ini kurang elegan karena Anda harus mendeklarasikan dan mendefinisikan variabel secara terpisah di file header dan cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub upstream .

Alternatif header saja Pre-C ++ 17

Ini tidak sebaik externsolusinya, tetapi berfungsi dan hanya menggunakan satu lokasi memori:

Sebuah constexprfungsi, karena constexprmenyiratkaninline dan inline memungkinkan (memaksa) definisi tersebut muncul di setiap unit terjemahan :

constexpr int shared_inline_constexpr() { return 42; }

dan saya yakin bahwa setiap kompiler yang layak akan melakukan panggilan inline.

Anda juga dapat menggunakan variabel constatau constexprstatis seperti di:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

tetapi Anda tidak dapat melakukan hal-hal seperti mengambil alamatnya, atau menjadi digunakan odr, lihat juga: Mendefinisikan anggota data statis constexpr

C

Dalam C situasinya sama dengan C ++ sebelum C ++ 17, saya telah mengunggah contoh di: Apa artinya "statis" di C?

Satu-satunya perbedaan adalah bahwa dalam C ++, constmenyiratkan staticuntuk GLOBALS, tapi tidak di C: C ++ semantik `const` statis vs` const`

Adakah cara untuk membuatnya sebaris sepenuhnya?

TODO: apakah ada cara untuk menyebariskan variabel sepenuhnya, tanpa menggunakan memori sama sekali?

Mirip seperti yang dilakukan preprocessor.

Ini akan membutuhkan entah bagaimana:

  • melarang atau mendeteksi jika alamat variabel diambil
  • menambahkan informasi tersebut ke file objek ELF, dan biarkan LTO mengoptimalkannya

Terkait:

Diuji di Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
2

Ya, itu menyembunyikan variabel dalam modul dari modul lain. Di C ++, saya menggunakannya ketika saya tidak ingin / perlu mengubah file .h yang akan memicu pembuatan ulang file lain yang tidak perlu. Juga, saya menempatkan statis terlebih dahulu:

static const int foo = 42;

Selain itu, bergantung pada penggunaannya, compiler bahkan tidak akan mengalokasikan penyimpanan untuknya dan hanya "menyebariskan" nilai tempatnya digunakan. Tanpa statik, kompilator tidak dapat menganggap itu tidak digunakan di tempat lain dan tidak bisa sebaris.

Jim Buck
sumber
2

Konstanta global ini hanya terlihat / dapat diakses dalam modul kompilasi (file .cpp). BTW yang menggunakan statis untuk tujuan ini tidak digunakan lagi. Lebih baik gunakan namespace anonim dan enum:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
sumber
ini akan memaksa kompilator untuk tidak memperlakukan foo sebagai sebuah konstanta dan dengan demikian menghalangi pengoptimalan.
Nils Pipenbrinck
nilai enum selalu konstan jadi saya tidak melihat bagaimana ini akan menghalangi pengoptimalan apa pun
Roskoto
ah - benar .. kesalahanku. mengira Anda telah menggunakan variabel int sederhana.
Nils Pipenbrinck
Roskoto, saya tidak mengerti manfaat apa yang enumdimiliki dalam konteks ini. Mau menjelaskan lebih lanjut? Hal enumstersebut biasanya hanya digunakan untuk mencegah kompilator mengalokasikan ruang apa pun untuk nilai tersebut (meskipun kompiler modern tidak memerlukan enumperetasan ini untuk itu) dan untuk mencegah pembuatan pointer ke nilai.
Konrad Rudolph
Konrad, Apa sebenarnya masalah yang Anda lihat dalam menggunakan enum dalam kasus ini? Enum digunakan ketika Anda membutuhkan int konstan yang persis seperti itu.
Roskoto
1

Menjadikannya pribadi tetap berarti itu muncul di header. Saya cenderung menggunakan cara "terlemah" yang berhasil. Lihat artikel klasik ini oleh Scott Meyers: http://www.ddj.com/cpp/184401197 (ini tentang fungsi, tetapi dapat diterapkan di sini juga).

thn
sumber