C ++ 'da global sabiti tanımlama


81

Birkaç kaynak dosyada görünmesi için C ++ 'da bir sabit tanımlamak istiyorum. Bunu bir başlık dosyasında tanımlamanın aşağıdaki yollarını hayal edebiliyorum:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Değeri döndüren bazı işlevler (örneğin int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; ve tek bir kaynak dosyada const int GLOBAL_CONST_VAR = 0xFF;

Seçenek (1) - kesinlikle kullanmak isteyeceğiniz seçenek değil

Seçenek (2) - başlık dosyasını kullanarak her nesne dosyasındaki değişkenin örneğini tanımlama

Seçenek (3) - IMO çoğu durumda aşırı öldürüyor

Seçenek (4) - çoğu durumda, enum somut türü olmadığından iyi olmayabilir (C ++ 0X, türü tanımlama olasılığını ekleyecektir)

Bu yüzden çoğu durumda (5) ve (6) arasında seçim yapmam gerekiyor. Sorularım:

  1. (5) veya (6) neyi tercih edersiniz?
  2. Neden (5) tamam, (2) değil?

1
5'e karşı 2: "const" dahili bağlantı anlamına gelir. Bu sürüm-5 başlığını birden çok çeviri birimine dahil ettiğinizde, "tek tanımlama kuralı" nı ihlal etmiş olmayacaksınız. Ayrıca, const, derleyicinin "sabit bölme" yapmasına izin verirken, const olmayan değişkenin değeri değişebilir. Seçenek 6 yanlış. Harici bağlantıyı zorlamak için cpp dosyasında "extern" e de ihtiyacınız var, aksi takdirde linker hataları alırsınız. Seçenek 6, değeri gizleme avantajına sahiptir. Ama aynı zamanda sürekli katlamayı imkansız kılar.
sellibitze

Yanıtlar:


32

(5) tam olarak söylemek istediğinizi söylüyor. Ayrıca, derleyicinin çoğu zaman onu optimize etmesini sağlar. (6) Öte yandan, derleyicinin onu en sonunda değiştirip değiştirmeyeceğinizi bilemeyeceği için derleyicinin onu hiçbir zaman optimize etmesine izin vermez.


1
OTOH, 5, ODR'nin ihlali olarak teknik olarak yasa dışıdır. Ancak çoğu derleyici bunu görmezden gelir.
Joel

Eh, bunu hiçbir şey tanımlamadığını düşünmeyi tercih ederim, derleyiciye bir numaraya güzel bir isim vermesini söyledim. Tüm niyet ve amaçlar için (5) budur, yani çalışma zamanında ek yük yoktur.
Blindy

2
(5) ODR'nin ihlali midir? Eğer öyleyse, (6) tercih edilir. (6) durumunda derleyici neden "değiştirip değiştirmeyeceğinizi bilmiyor" ? extern const int ...ve const int ...ikisi de sabit değil mi?
D.Shawley

4
AFAIK, 5) ile 6) arasında, yalnızca 6) sabitin türü int tabanlı olmadığında izin verilir.
Klaim

11
ODR ihlali yoktur, sabit nesneler varsayılan olarak statiktir.
avakar

71

Kesinlikle seçenek 5 ile gidin - türü güvenlidir ve derleyicinin optimize etmesine izin verir (bu değişkenin adresini almayın :) Ayrıca bir başlık içindeyse - genel kapsamı kirletmekten kaçınmak için bir ad alanına yapıştırın:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;

3
header.hppBirkaç kaynak dosyaya eklemeye çalıştığımda yeniden tanımlama hatası alıyorum .
LRDPRDX

Bunun neden hala yükseltildiğinden emin değilim - neredeyse on yıl oldu, ama bugünlerde böyle constexprşeyler için numaralandırmalar yaptık ve yazdık .
Nikolai Fetissov

24

(5), (6) 'dan "daha iyidir" çünkü GLOBAL_CONST_VARtüm çeviri birimlerinde İntegral Sabit İfadesi (ICE) olarak tanımlanır. Örneğin, bunu tüm çeviri birimlerinde dizi boyutu ve durum etiketi olarak kullanabileceksiniz. (6) olması durumunda, GLOBAL_CONST_VARsadece tanımlandığı yerde ve sadece tanım noktasından sonra bir ICE olacaktır. Diğer çeviri birimlerinde ICE olarak çalışmaz.

Bununla birlikte, (5) 'in GLOBAL_CONST_VARdahili bağlantı sağladığını unutmayın , yani "adres kimliği" GLOBAL_CONST_VARher çeviri biriminde farklı olacaktır, yani her çeviri biriminde &GLOBAL_CONST_VARsize farklı bir işaretçi değeri verecektir. Çoğu kullanım durumunda bu önemli değildir, ancak tutarlı global "adres kimliğine" sahip sabit bir nesneye ihtiyacınız varsa, o zaman (6) ile devam etmeniz ve sabitin ICE-lığını feda etmeniz gerekir. süreç.

Ayrıca, sabitin ICE-lesi bir sorun olmadığında (integral bir tip değil) ve tipin boyutu büyüdüğünde (skaler tip değil), o zaman (6) genellikle (5) 'ten daha iyi bir yaklaşım haline gelir.

(2) tamam değil çünkü GLOBAL_CONST_VARin (2) varsayılan olarak harici bağlantıya sahip. Bunu başlık dosyasına koyarsanız, genellikle GLOBAL_CONST_VARbir hata olan birden çok tanımıyla karşılaşırsınız . constC ++ 'daki nesneler varsayılan olarak dahili bağlantıya sahiptir, bu nedenle (5) çalışır (ve bu nedenle, yukarıda söylediğim gibi, GLOBAL_CONST_VARher çeviri biriminde ayrı, bağımsız bir tane elde edersiniz ).


C ++ 17'den başlayarak bildirme seçeneğiniz vardır

inline extern const int GLOBAL_CONST_VAR = 0xFF;

bir başlık dosyasında. Bu size tüm çeviri birimlerinde (aynı yöntem (5) gibi) aynı zamanda global adres kimliğini koruyan bir ICE verir GLOBAL_CONST_VAR- tüm çeviri birimlerinde aynı adrese sahip olacaktır.


8

C ++ 11 veya sonraki bir sürümünü kullanıyorsanız, derleme zamanı sabitlerini kullanmayı deneyin:

constexpr int GLOBAL_CONST_VAR{ 0xff };

1
IMHO, bu sorunun tek tatmin edici çözümü budur.
lanoxx

5

Eğer sabit olacaksa, onu sabit olarak işaretlemelisiniz - bu yüzden bence 2 kötüdür.

Derleyici, bazı matematiği ve aslında değeri kullanan diğer işlemleri genişletmek için değerin const niteliğini kullanabilir.

5 ile 6 - hmm arasında seçim; 5 bana daha iyi geliyor.

6) 'da değer gereksiz yere bildiriminden ayrılmıştır.

Tipik olarak bu başlıklardan bir veya daha fazlasına sahip olurdum, sadece sabitleri vb. Tanımlar ve sonra başka 'akıllı' şeyler olmaz - her yere kolayca dahil edilebilecek hoş hafif başlıklar.


3
(6) gereksiz ayrılma değil, bilinçli bir seçimdir. Büyük olan çok sayıda sabitiniz varsa, bunları (6) 'da olduğu gibi bildirmezseniz çalıştırılabilir dosyada çok fazla alan harcarsınız. Matematik kütüphanelerinde olabilir ... israf 100.000'in altında olabilir ama bu bile bazen önemlidir. (Bazı derleyicilerin bunu aşmanın başka yolları vardır, sanırım MSVC'nin "bir kez" özelliği veya benzer bir özelliği vardır.)
Dan Olson

(5) ile sabit kalacağından gerçekten emin olamazsınız (her zaman sabitliği bir kenara atabilirsiniz). Bu yüzden hala enum türünü tercih ederim.
fmuecke

@Dan Olson - bu çok iyi bir nokta - Cevabım burada yer alan türün int olduğu gerçeğine dayanıyordu; ancak daha büyük değerlerle uğraşırken, extern beyanı gerçekten daha iyi bir plandır.
Andras Zoltan

@fmuecke - Evet, haklısınız - bu durumda enum değeri bunu engeller. Ama bu, değerlerimizi her zaman bu şekilde yazılardan korumamız gerektiği anlamına mı geliyor? Bir programcı kodu kötüye kullanmak isterse, bir (target_type *) ((void *) & value) karakterinin hasara yol açabileceği, yakalayamayacağımız, bazen onlara güvenmemiz gereken pek çok alan vardır; ve gerçekten kendimiz, değil mi?
Andras Zoltan

@fmuecke Const olarak bildirilen bir değişken program tarafından değiştirilemez (bunu yapmaya çalışmak tanımsız bir davranıştır). const_cast yalnızca orijinal değişkenin const olarak bildirilmediği durumlarda tanımlanır (örneğin, const olmayan bir değeri bir işleve bir const & olarak geçirmek).
David Stone

5

İkinci sorunuzu cevaplamak için:

(2), Tek Tanım Kuralı'nı ihlal ettiği için yasa dışıdır. İçerdiği GLOBAL_CONST_VARher dosyada, yani birden çok kez tanımlar . (5) yasaldır çünkü Tek Tanım Kuralı'na tabi değildir. Her biri GLOBAL_CONST_VAR, dahil edildiği dosya için yerel olan ayrı bir tanımdır. Elbette tüm bu tanımlar aynı adı ve değeri paylaşır, ancak adresleri farklı olabilir.


4

C ++ 17 inline değişkenler

Bu harika C ++ 17 özelliği şunları yapmamızı sağlar:

  • her bir sabit için sadece tek bir bellek adresini rahatlıkla kullanın
  • şu şekilde saklayın constexpr: constexpr extern nasıl beyan edilir?
  • bunu bir başlıktan tek bir satırda yapın

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;
}

Derleyin ve çalıştırın:

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 yukarı akış .

Ayrıca bkz: Satır içi değişkenler nasıl çalışır?

Satır içi değişkenlerde C ++ standardı

C ++ standardı, adreslerin aynı olacağını garanti eder. C ++ 17 N4659 standart taslak 10.1.6 "Satır içi tanımlayıcı ":

6 Dış bağlantılı bir satır içi işlev veya değişken, tüm çeviri birimlerinde aynı adrese sahip olmalıdır.

cppreference https://en.cppreference.com/w/cpp/language/inline , staticverilmezse, harici bağlantıya sahip olduğunu açıklar .

Satır içi değişken uygulaması

Nasıl uygulandığını şu şekilde gözlemleyebiliriz:

nm main.o notmain.o

içerenler:

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

ve man nmşöyle diyor u:

"u" Sembol, benzersiz bir küresel semboldür. Bu, standart ELF simge bağlamaları kümesinin bir GNU uzantısıdır. Böyle bir sembol için dinamik bağlayıcı, tüm süreçte bu isim ve türe sahip tek bir sembolün kullanımda olduğundan emin olacaktır.

bu yüzden bunun için özel bir ELF uzantısı olduğunu görüyoruz.

GCC 7.4.0, Ubuntu 18.04'te test edilmiştir.


2
const int GLOBAL_CONST_VAR = 0xFF;

çünkü sabittir!


1
Ve makro gibi muamele görmeyeceğinden, hata ayıklamayı kolaylaştırır.
kayleeFrye_onDeck

-1, Bu, başlık birden çok kaynak dosyaya dahil edildiğinde yeniden tanımlama uyarılarına / hatalarına yol açacaktır. Ayrıca bu cevap Nikolai Fetissov'un cevabının bir kopyasıdır.
lanoxx

1

Gereksinimlerinize bağlıdır. (5) çoğu normal kullanım için en iyisidir, ancak çoğu zaman her nesne dosyasında sürekli depolama alanı kaplamasına neden olur. (6) önemli olduğu durumlarda bunun üstesinden gelebilir.

(4) ayrıca, önceliğiniz depolama alanının asla tahsis edilmeyeceğini garanti ediyorsa iyi bir seçimdir, ancak elbette yalnızca integral sabitler için çalışır.


1
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.