C ++에서 전역 상수 정의 0xFF int GLOBAL_CONST_VAR = 0xFF; 값을

여러 소스 파일에서 볼 수 있도록 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)는 괜찮습니까?


답변

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


답변

확실히 옵션 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;

답변

(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. 모든 번역 단위에서 동일한 주소를 갖게됩니다.


답변

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

constexpr int GLOBAL_CONST_VAR{ 0xff };

답변

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

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

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

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

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


답변

두 번째 질문에 답하려면 :

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


답변

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에서 테스트되었습니다.