C ++에서 전역 상수 정의


81

여러 소스 파일에서 볼 수 있도록 C ++에서 상수를 정의하고 싶습니다. 헤더 파일에서 정의하는 다음과 같은 방법을 상상할 수 있습니다.

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. 값을 returing 일부 기능 (예를 들어 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; 그리고 하나의 소스 파일에서 const int GLOBAL_CONST_VAR = 0xFF;

옵션 (1)-확실히 사용하고 싶은 옵션이 아닙니다.

옵션 (2)-헤더 파일을 사용하여 각 개체 파일의 변수 인스턴스 정의

옵션 (3)-IMO는 대부분의 경우 과잉 살인입니다.

옵션 (4)-enum에 구체적인 유형이 없기 때문에 많은 경우 좋지 않을 수 있습니다 (C ++ 0X는 유형을 정의 할 가능성을 추가합니다).

따라서 대부분의 경우 (5)와 (6) 중에서 선택해야합니다. 내 질문 :

  1. (5) 또는 (6) 무엇을 선호합니까?
  2. 왜 (5)는 괜찮고 (2)는 괜찮습니까?

1
5 대 2 : "const"는 내부 연결을 의미합니다. 이 버전 -5 헤더를 여러 번역 단위에 포함하면 "하나의 정의 규칙"을 위반하지 않습니다. 또한 const를 사용하면 컴파일러가 "상수 폴딩"을 수행 할 수 있지만 상수가 아닌 변수의 값은 변경 될 수 있습니다. 옵션 6이 잘못되었습니다. 외부 연결을 강제하려면 cpp 파일에 "extern"이 필요합니다. 그렇지 않으면 링커 오류가 발생합니다. 옵션 6은 가치를 숨기는 장점이 있습니다. 그러나 그것은 또한 지속적인 접기를 불가능하게 만듭니다.
sellibitze 2010

답변:


32

(5) 당신이 말하고 싶은 것을 정확하게 말합니다. 또한 컴파일러가 대부분의 시간을 최적화 할 수 있습니다. (6) 반면에 컴파일러는 당신이 결국 그것을 변경할지 여부를 알지 못하기 때문에 컴파일러가 그것을 최적화하지 못하게 할 것입니다.


1
OTOH, 5는 ODR 위반으로 기술적으로 불법입니다. 그러나 대부분의 컴파일러는이를 무시합니다.
Joel

어, 나는 아무것도 정의하지 않은 것으로 생각하는 것을 선호합니다. 컴파일러에게 숫자에 예쁜 이름을 주라고 말했습니다. 모든 의도와 목적을 위해 (5)는 런타임에 오버 헤드가 없음을 의미합니다.
Blindy 2010

2
(5) ODR 위반입니까? 그렇다면 (6)이 바람직합니다. (6)의 경우 컴파일러가 "변경할 것인지 알지 못하는" 이유는 무엇 입니까? extern const int ...그리고 const int ...모두 일정한 그들은하지 않습니다?
D.Shawley 2010

4
AFAIK, 5)와 6) 사이, 상수 유형이 int 기반이 아닌 경우 6) 만 허용됩니다.
Klaim 2010

11
ODR 위반이 없으며 상수 개체는 기본적으로 정적입니다.
avakar

71

확실히 옵션 5로 가십시오-유형이 안전하고 컴파일러가 최적화 할 수 있도록합니다 (해당 변수의 주소를 사용하지 마십시오 :) 또한 헤더에있는 경우 전역 범위를 오염시키지 않도록 네임 스페이스에 붙여 넣으십시오.

// 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.hpp여러 소스 파일 에 포함하려고하면 재정의 오류가 발생 합니다.
LRDPRDX

왜 이것이 여전히 찬성 투표를 받는지 잘 모르겠습니다. 거의 10 년이 지났지 만 요즘에는 constexpr그런 것에 대한 열거 형을 가지고 입력했습니다.
Nikolai Fetissov

23

(5)는 GLOBAL_CONST_VAR모든 번역 단위에서 ICE (Integral Constant Expression)로 정의되기 때문에 (6)보다 "낫습니다" . 예를 들어, 모든 번역 단위에서 배열 크기 및 케이스 레이블로 사용할 수 있습니다. (6)의 경우 GLOBAL_CONST_VAR정의 된 번역 단위에서 정의 지점 이후에만 ICE가됩니다. 다른 번역 단위에서는 ICE로 작동하지 않습니다.

그러나 (5)는 GLOBAL_CONST_VAR내부 연결을 제공합니다 . 즉,의 "주소 ID"가 GLOBAL_CONST_VAR각 번역 단위에서 &GLOBAL_CONST_VAR다를 것입니다. 즉, 각 번역 단위에서 다른 포인터 값을 제공합니다. 대부분의 사용 사례에서 이것은 중요하지 않지만 일관된 전역 "주소 ID"를 갖는 상수 개체가 필요한 경우 (6)을 사용하여 상수의 ICE-ness를 희생해야합니다. 방법.

또한 상수의 ICE-ness가 문제가되지 않고 (정수 유형이 아님) 유형의 크기가 커지면 (스칼라 유형이 아님) 일반적으로 (6)이 (5)보다 더 나은 접근 방식이됩니다.

(2)는 GLOBAL_CONST_VAR기본적으로 외부 연결이 있기 때문에 OK가 아닙니다 . 헤더 파일에 넣으면 일반적으로에 대한 여러 정의가 발생 GLOBAL_CONST_VAR하며 이는 오류입니다. constC ++의 객체는 기본적으로 내부 연결이 있습니다. 이것이 (5)가 작동하는 이유입니다 (위에서 말했듯 GLOBAL_CONST_VAR이 각 번역 단위에서 별도의 독립적 인 이유 ).


C ++ 17부터 선언 할 수있는 옵션이 있습니다.

inline extern const int GLOBAL_CONST_VAR = 0xFF;

헤더 파일에서. 이렇게하면 모든 번역 단위 (방법 (5)와 마찬가지로)에서 ICE를 제공하는 동시에 전역 주소 ID를 유지합니다 GLOBAL_CONST_VAR. 모든 번역 단위에서 동일한 주소를 갖게됩니다.


8

C ++ 11 이상을 사용하는 경우 컴파일 타임 상수를 사용해보십시오.

constexpr int GLOBAL_CONST_VAR{ 0xff };

1
IMHO, 이것이이 문제에 대한 유일한 만족스러운 해결책입니다.
lanoxx

5

상수가 되려면 상수로 표시해야합니다. 이것이 제 생각에는 2가 나쁜 이유입니다.

컴파일러는 값의 const 특성을 사용하여 일부 수학 및 실제로 값을 사용하는 다른 연산을 확장 할 수 있습니다.

5에서 6 사이의 선택-흠; 5 기분이 나아졌습니다.

6)에서 값은 선언에서 불필요하게 분리됩니다.

나는 일반적으로 상수 등 만 정의하는 이러한 헤더 중 하나 이상을 가지고 있으며 다른 '영리한'것은 없습니다. 어디서나 쉽게 포함될 수있는 멋진 경량 헤더.


3
(6) 불필요한 분리가 아니라 의도적 인 선택입니다. 큰 상수가 많은 경우 (6)에서와 같이 선언하지 않으면 실행 파일에서 많은 공간을 낭비합니다. 수학 도서관에서 이런 일이 발생할 수 있습니다. 낭비는 10 만 미만일 수 있지만 때로는 중요합니다. (일부 컴파일러에는이 문제를 해결할 수있는 다른 방법이 있습니다. MSVC에는 "한 번"속성 또는 이와 유사한 것이 있다고 생각합니다.)
Dan Olson

(5)를 사용하면 그것이 const로 남아있을 것이라고 확신 할 수 없습니다 (항상 constness를 버릴 수 있습니다). 이것이 내가 여전히 enum 유형을 선호하는 이유입니다.
fmuecke 2010

@Dan Olson-그것은 아주 좋은 지적입니다-내 대답은 여기에 관련된 유형이 int라는 사실에 근거했습니다. 그러나 더 큰 가치를 다룰 때 extern 선언은 실제로 더 나은 계획입니다.
안드라스 졸탄

@fmuecke-예, 맞습니다-열거 형 값이이를 방지합니다. 그러나 그것은 우리가 항상 이런 방식으로 쓰기로부터 우리의 가치를 보호해야한다는 것을 의미합니까? 프로그래머가 코드를 남용하려는 경우 (target_type *) ((void *) & value) 캐스트가 혼란을 일으킬 수있는 영역이 너무 많아서 잡을 수없고 때로는 신뢰를해야하는 경우도 있습니다. 그리고 실제로 우리 자신이 아닙니다.
Andras Zoltan

@fmuecke const로 선언 된 변수는 프로그램에 의해 변경 될 수 없습니다 (그렇게 시도하는 것은 정의되지 않은 동작입니다). const_cast는 원래 변수가 const로 선언되지 않은 상황에서만 정의됩니다 (예 : 상수가 아닌 값을 const &로 함수에 전달).
David Stone

5

두 번째 질문에 답하려면 :

(2) 단일 정의 규칙을 위반하여 불법입니다. GLOBAL_CONST_VAR포함 된 모든 파일, 즉 두 번 이상 정의합니다 . (5)는 하나의 정의 규칙이 적용되지 않기 때문에 합법적입니다. 각각 GLOBAL_CONST_VAR은 포함 된 해당 파일에 로컬 인 별도의 정의입니다. 이러한 모든 정의는 물론 동일한 이름과 값을 공유하지만 주소는 다를 수 있습니다.


4

C ++ 17 inline변수

이 멋진 C ++ 17 기능을 통해 다음을 수행 할 수 있습니다.

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

컴파일 및 실행 :

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 업스트림 .

참고 항목 : 인라인 변수는 어떻게 작동합니까?

인라인 변수에 대한 C ++ 표준

C ++ 표준은 주소가 동일하다는 것을 보장합니다. C ++ 17 N4659 표준 초안 10.1.6 "인라인 지정자":

6 외부 연결이있는 인라인 함수 또는 변수는 모든 변환 단위에서 동일한 주소를 가져야합니다.

cppreference https://en.cppreference.com/w/cpp/language/inlinestatic 지정되어 있지 않은 경우, 그것은 외부 링크가 있습니다.

인라인 변수 구현

다음과 같이 구현되는 방법을 관찰 할 수 있습니다.

nm main.o notmain.o

포함하는:

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

그리고 man nm말한다 u:

"u"기호는 고유 한 글로벌 기호입니다. 이것은 ELF 심볼 바인딩의 표준 세트에 대한 GNU 확장입니다. 이러한 심볼의 경우 동적 링커는 전체 프로세스에서이 이름과 유형이 사용중인 심볼이 하나만 있는지 확인합니다.

이를위한 전용 ELF 확장이 있음을 알 수 있습니다.

GCC 7.4.0, Ubuntu 18.04에서 테스트되었습니다.


2
const int GLOBAL_CONST_VAR = 0xFF;

상수이기 때문에!


1
또한 매크로처럼 취급되지 않으므로 디버깅이 더 쉬워집니다.
kayleeFrye_onDeck

-1, 여러 소스 파일에 헤더를 포함 할 때 경고 / 오류를 재정의합니다. 또한이 답변은 Nikolai Fetissov의 답변과 중복됩니다.
lanoxx

1

귀하의 요구 사항에 따라 다릅니다. (5)는 대부분의 일반적인 사용에 가장 적합하지만 종종 모든 개체 파일에서 저장 공간을 계속 차지하게됩니다. (6) 중요한 상황에서이 문제를 해결할 수 있습니다.

(4) 또한 우선 순위가 저장 공간이 할당되지 않도록 보장하는 경우 적절한 선택이지만 물론 정수 상수에 대해서만 작동합니다.


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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.