C 및 C ++에서 'const static'은 무엇을 의미합니까?


117
const static int foo = 42;

StackOverflow의 일부 코드에서 이것을 보았고 그것이 무엇을하는지 알 수 없었습니다. 그런 다음 다른 포럼에서 혼란스러운 답변을 보았습니다. 내 추측으로는 foo다른 모듈 에서 상수를 숨기기 위해 C에서 사용된다는 것 입니다. 이 올바른지? 그렇다면 왜 누군가가 그것을 만들 수있는 C ++ 컨텍스트에서 그것을 사용 private합니까?

답변:


113

그것은 C와 C ++ 모두에서 사용됩니다.

짐작했듯이 static파트는 범위를 해당 컴파일 단위 로 제한합니다 . 또한 정적 초기화를 제공합니다. const컴파일러에게 아무도 수정하지 못하도록 지시합니다. 이 변수는 아키텍처에 따라 데이터 또는 bss 세그먼트에 배치되며 읽기 전용으로 표시된 메모리에있을 수 있습니다.

이것이 C가 이러한 변수를 처리하는 방법 (또는 C ++이 네임 스페이스 변수를 처리하는 방법)입니다. C ++에서 표시된 멤버 static는 지정된 클래스의 모든 인스턴스에서 공유됩니다. 비공개인지 여부는 하나의 변수가 여러 인스턴스에서 공유된다는 사실에 영향을주지 않습니다. 데 const코드가를 수정하려고한다면 거기에 당신을 경고합니다.

엄격하게 비공개 인 경우 클래스의 각 인스턴스는 고유 한 버전을 얻습니다 (최적화에도 불구하고).


1
원래 예제는 "개인 변수"에 대해 이야기하고 있습니다. 따라서 이것은 mebmer이며 정적은 연결에 영향을 미치지 않습니다 . "정적 부분은 해당 파일의 범위를 제한"을 제거해야합니다.
Richard Corden

"특수 섹션"은 명시 적 "문자열"및 전역 배열과 같은 다른 모든 전역 변수와 공유하는 데이터 세그먼트로 알려져 있습니다. 이것은 코드 세그먼트에 반대됩니다.
spoulson

@Richard-클래스의 구성원이라고 생각하는 이유는 무엇입니까? 그것이 말하는 질문에 아무것도 없습니다. 경우 는 클래스의 일원이다, 당신 말이 맞아,하지만 그것은 단지의 경우 변수가 전역 범위에서 선언 한 후 크리스는 권리입니다.
Graeme Perrow

1
원래 포스터는 가능한 더 나은 해결책으로 비공개를 언급했지만 원래 문제는 아닙니다.
Chris Arguin

@Graeme, OK 그래서 그것은 "확실히"멤버가 아닙니다. 그러나이 대답은 네임 스페이스 멤버에만 적용되는 문을 만들고 있으며 그 문은 멤버 변수에 대해 잘못되었습니다. 오류가 언어에 익숙하지 않은 사람을 혼동 할 수있는 투표 수를 감안할 때 수정해야합니다.
Richard Corden

212

많은 사람들이 기본적인 대답을하는 듯했으나 아무도 C ++에서 지적하지 const기본값 static에서 namespace레벨 (일부는 잘못된 정보를 주었다). C ++ 98 표준 섹션 3.5.3을 참조하십시오.

먼저 배경 :

번역 단위 : 전 처리기 (재귀 적으로)가 모든 포함 파일을 포함시킨 후의 소스 파일.

정적 연결 : 기호는 번역 단위 내에서만 사용할 수 있습니다.

외부 연결 : 다른 번역 단위에서 기호를 사용할 수 있습니다.

에서 namespace수준

여기에는 전역 이름 공간 일명 전역 변수가 포함 됩니다.

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

기능 수준에서

static함수 호출 사이에 값이 유지됨을 의미합니다.
함수 static변수 의 의미는 프로그램의 데이터 세그먼트 (스택이나 힙이 아님) 에 있다는 점에서 전역 변수와 유사합니다 . 변수 수명 에 대한 자세한 내용 은 이 질문 을 참조하십시오 static.

에서 class수준

static값이 클래스의 모든 인스턴스간에 공유되며 const변경되지 않음을 의미합니다.


2
기능 수준에서 정적이 const를 통한 reduntant되지 않으며, 그들은 다르게 작동 할 수있는 const int *foo(int x) {const int b=x;return &b};const int *foo(int x) {static const int b=x;return &b};
Hanczar

1
질문은 C와 C ++ 모두에 관한 것이므로 후자 const에만 암시하는 것에 대한 메모를 포함해야합니다 static.
Nikolai Ruhe 2013-06-06

@Motti : 좋은 대답입니다. 기능 수준에서 무엇이 중복되는지 설명해 주시겠습니까? 당신이 말하고있는 const선언을 의미 static뿐만 아니라이? 에서 const와 같이 값을 버리고 수정하면 모든 값이 수정됩니까?
Cookie

1
@Motti const는 함수 수준에서 정적을 의미하지 않습니다. 그것은 동시성 악몽 (const! = constant expression)이 될 것이며, 함수 수준의 모든 것은 암시 적으로 auto. 이 질문에는 [c] 태그도 지정되어 있으므로 전역 수준 const intexternC에서 암시 적 으로 사용 된다는 점을 언급해야합니다 . 그러나 여기에있는 규칙은 C ++를 완벽하게 설명합니다.
Ryan Haining

1
그리고 C ++에서 세 가지 경우 모두 static변수가 정적 기간 (프로그램 시작부터 끝까지 지속되는 복사본이 하나만 존재 함)이고, 달리 지정되지 않은 경우 내부 / 정적 연결이 있음을 나타냅니다 (이는 함수의 로컬 정적 변수에 대한 연결 또는 정적 멤버에 대한 클래스의 연결). 주요 차이점 static은 유효한 각 상황에서 이것이 의미하는 바에 있습니다.
저스틴 시간 - 분석 재개 모니카

45

이 코드 줄은 실제로 여러 다른 컨텍스트에서 나타날 수 있으며 거의 ​​동일하게 작동하지만 약간의 차이가 있습니다.

네임 스페이스 범위

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

' i'은 헤더를 포함하는 모든 번역 단위에 표시됩니다. 그러나 실제로 객체의 주소 (예 : ' &i')를 사용하지 않는 한 , 컴파일러는 ' i'를 단순히 형식에 안전한 것으로 취급 할 것이라고 확신합니다 0. 두 개 이상의 번역 단위가 ' &i'를 사용하면 각 번역 단위에 대해 주소가 달라집니다.

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

' i'에는 내부 연결이 있으므로이 번역 단위 외부에서 참조 할 수 없습니다. 그러나 주소를 사용하지 않는 한 다시 type-safe로 취급 될 가능성이 높습니다 0.

지적 할 가치가있는 한 가지는 다음 선언입니다.

const int i1 = 0;

정확히 동일합니다 static const int i = 0. 로 선언 const되고 명시 적으로 선언되지 않은 네임 스페이스의 변수 extern는 암시 적으로 정적입니다. 이것에 대해 생각해 보면 , ODR을 깨는 것을 피하기 위해 const항상 static키워드를 필요로하지 않고 헤더 파일에서 변수를 선언 할 수 있도록하는 것이 C ++위원회의 의도였습니다 .

클래스 범위

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

위의 예에서 표준 i은 주소가 필요하지 않은 경우 ' '를 정의 할 필요가 없음을 명시 적으로 지정 합니다. 즉, ' i'를 유형 안전 0으로 만 사용 하면 컴파일러가이를 정의하지 않습니다. 클래스와 네임 스페이스 버전의 한 가지 차이점은 ' i' 의 주소 (두 개 이상의 번역 단위에서 사용되는 경우)가 클래스 멤버에 대해 동일하다는 것입니다. 주소가 사용되는 경우 해당 주소에 대한 정의가 있어야합니다.

// 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

2
+1은 정적 const가 네임 스페이스 범위의 const와 동일하다는 것을 나타냅니다.
Plumenator

번역 단위를 컴파일 할 때 .h가 포함되기 때문에 "foo.h"또는 "foo.cc"에 배치하는 것은 실제로 차이가 없습니다.
Mikhail

2
@Mikhail : 맞아요. 헤더가 여러 TU에 포함될 수 있다는 가정이 있으므로 별도로 이야기하는 것이 유용했습니다.
Richard Corden

24

작은 공간 최적화입니다.

당신이 말할 때

const int foo = 42;

상수를 정의하는 것이 아니라 읽기 전용 변수를 만듭니다. 컴파일러는 foo를 볼 때마다 42를 사용할만큼 똑똑하지만 초기화 된 데이터 영역에 공간을 할당하기도합니다. 이것은 정의 된대로 foo에 외부 연결이 있기 때문에 수행됩니다. 다른 컴파일 단위는 다음과 같이 말할 수 있습니다.

extern const int foo;

그 가치에 접근하기 위해. 그 컴파일 단위는 foo의 값이 무엇인지 전혀 모르기 때문에 좋은 습관이 아닙니다. 그것은 단지 그것이 const int라는 것을 알고 있고 그것이 사용될 때마다 메모리에서 값을 다시로드해야합니다.

이제 정적임을 선언하여 :

static const int foo = 42;

컴파일러는 일반적인 최적화를 수행 할 수 있지만 "야,이 컴파일 단위 외부의 누구도 foo를 볼 수 없으며 항상 42라는 것을 알고 있으므로 공간을 할당 할 필요가 없습니다."라고 말할 수도 있습니다.

또한 C ++에서 이름이 현재 컴파일 단위를 이스케이프하지 않도록 방지하는 가장 좋은 방법은 익명 네임 스페이스를 사용하는 것입니다.

namespace {
    const int foo = 42; // same as static definition above
}

1
, u는 정적 "초기화 된 데이터 영역에 공간을 할당합니다"를 사용하지 않고 언급했습니다. 정적 "공간을 할당 할 필요가 없습니다."(컴파일러가 val을 고르는 곳에서?)를 사용하여 변수가 저장되는 힙 및 스택 측면에서 설명 할 수 있습니다. 해석하는 경우 수정 해주세요. 잘못된 .
Nihar 2015 년

@ N.Nihar-정적 데이터 영역은 정적 연결이있는 모든 데이터를 포함하는 고정 된 크기의 메모리 청크입니다. 프로그램을 메모리에로드하는 과정에 의해 "할당"됩니다. 스택이나 힙의 일부가 아닙니다.
Ferruccio 2015 년

함수가 foo에 대한 포인터를 반환하면 어떻게됩니까? 그게 최적화를 깨 뜨리나요?
nw.

@nw : 예, 그래야합니다.
Ferruccio

8

'int'가 없습니다. 그것은해야한다:

const static int foo = 42;

C 및 C ++에서는 로컬 파일 범위 값이 42 인 정수 상수를 선언합니다.

왜 42일까요? 당신이 아직 모른다면 (그리고 당신이 모른다는 것을 믿기 힘들다), 그것은 생명, 우주, 모든 것에 대한 답에 대한 언급 입니다.


고마워요 ... 이제 매번 ... 내 남은 생애 동안 ... 42를 보면 항상 이것에 대해 말할 것입니다. haha
Inisheer

이것은 우주가 13 개의 손가락으로 만들어 졌다는 증거입니다 (질문과 대답은 실제로 13 진법에서 일치합니다).
paxdiablo

그 쥐. 각 발에 3 개의 발가락과 꼬리를 추가하면베이스 13이됩니다.
KeithB

실제로 선언에 'int'가 필요하지는 않지만 작성하는 것이 확실히 좋은 취향입니다. C는 항상 기본적으로 'int'유형을 가정합니다. 시도 해봐!
ephemient

"값 42의 로컬 파일 범위 포함"?? 아니면 전체 컴파일 단위입니까?
aniliitb10 2015 년

4

C ++에서

static const int foo = 42;

상수를 정의하고 사용하는 데 선호되는 방법입니다. 즉,

#define foo 42

유형 안전 시스템을 파괴하지 않기 때문입니다.


4

모든 훌륭한 답변에 작은 세부 사항을 추가하고 싶습니다.

플러그인 (예 : CAD 시스템에서로드 할 DLL 또는 .so 라이브러리)을 작성하면 static 은 다음 과 같은 이름 충돌을 방지하는 생명의 은인입니다.

  1. CAD 시스템은 "const int foo = 42;"가있는 플러그인 A를로드합니다. 그것에.
  2. 시스템은 "const int foo = 23;"이있는 플러그인 B를로드합니다. 그것에.
  3. 결과적으로 플러그인 B는 foo에 42 값을 사용합니다. 플러그인 로더는 이미 외부 링크가있는 "foo"가 있음을 인식하기 때문입니다.

더 나쁜 점 : 3 단계는 컴파일러 최적화, 플러그인로드 메커니즘 등에 따라 다르게 작동 할 수 있습니다.

두 개의 플러그인에서 두 개의 도우미 함수 (같은 이름, 다른 동작)로이 문제가 한 번 발생했습니다. 정적으로 선언하면 문제가 해결되었습니다.


두 플러그인 간의 이름 충돌에 대해 뭔가 이상해 보였기 때문에 m_hDfltHeap을 외부 링크가있는 핸들로 정의하는 많은 DLL 중 하나에 대한 링크 맵을 조사하게되었습니다. 물론 연결 맵에 _m_hDfltHeap으로 나열되어있는 모든 세계가보고 사용할 수 있습니다. 나는이 사실에 관한 모든 것을 잊었다.
David A. Gray

4

C99 / GNU99 사양에 따르면 :

  • static

    • 스토리지 클래스 지정자

    • 기본적으로 파일 수준 범위의 개체에는 외부 연결이 있습니다.

    • 정적 지정자가있는 파일 수준 범위의 개체에는 내부 연결이 있습니다.
  • const

    • 유형 한정자 (유형의 일부)

    • 바로 왼쪽 인스턴스에 적용된 키워드-예

      • MyObj const * myVar; -const 한정된 개체 유형에 대한 한정되지 않은 포인터

      • MyObj * const myVar; -규정되지 않은 객체 유형에 대한 const 규정 된 포인터

    • 가장 왼쪽 사용-변수가 아닌 개체 유형에 적용

      • const MyObj * myVar; -const 한정된 개체 유형에 대한 한정되지 않은 포인터

그러므로:

static NSString * const myVar; -내부 연결이있는 불변 문자열에 대한 상수 포인터.

static키워드가 없으면 변수 이름이 전역으로 만들어지고 응용 프로그램 내에서 이름 충돌이 발생할 수 있습니다.


4

C ++ 17 inline 변수

Google에서 "C ++ const static"을 검색했다면 실제로 사용하려는 것이 C ++ 17 인라인 변수 일 가능성이 높습니다. .

이 멋진 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/inline는 경우라고 설명 static지정되어 있지 않은 경우, 그것은 외부 링크가 있습니다.

GCC 인라인 변수 구현

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

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 확장이 있음을 알 수 있습니다.

C ++ 이전 17 : extern const

C ++ 17 이전과 C에서는를 사용하여 매우 유사한 효과를 얻을 수 있으며 extern const,이 경우 단일 메모리 위치가 사용됩니다.

단점 inline은 다음과 같습니다.

  • constexpr이 기술로 변수를 만드는 것은 불가능하며 , 다음 만 inline허용합니다. constexpr extern을 선언하는 방법?
  • 헤더와 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 업스트림 .

Pre-C ++ 17 헤더 전용 대안

이들은 extern솔루션 만큼 좋지는 않지만 작동하며 단일 메모리 위치 만 차지합니다.

constexpr때문에 기능, constexpr의미inlineinline 수 (힘) 정의는 모든 번역 단위에 표시합니다 :

constexpr int shared_inline_constexpr() { return 42; }

괜찮은 컴파일러가 호출을 인라인 할 것이라고 장담합니다.

다음 과 같이 const또는 constexpr정적 변수를 사용할 수도 있습니다 .

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

그러나 주소를 가져 오는 것과 같은 작업을 수행 할 수 없습니다. 그렇지 않으면 odr이 사용됩니다. 참조 : constexpr 정적 데이터 멤버 정의

C에서 상황은 C ++ 이전 C ++ 17과 동일합니다. 다음 위치에 예제를 업로드했습니다. C에서 "정적"은 무엇을 의미합니까?

유일한 차이점은 C ++에서는 전역을 const의미 static하지만 C에서는 그렇지 않다는 것입니다 .`static const` 대`const`의 C ++ 의미 체계

완전히 인라인 할 방법이 있습니까?

TODO : 메모리를 전혀 사용하지 않고 변수를 완전히 인라인 할 수있는 방법이 있습니까?

전처리 기가하는 것과 매우 비슷합니다.

이것은 어떻게 든 필요합니다.

  • 변수의 주소가 사용되는지 여부를 금지하거나 감지
  • 해당 정보를 ELF 개체 파일에 추가하고 LTO가이를 최적화하도록합니다.

관련 :

Ubuntu 18.10, GCC 8.2.0에서 테스트되었습니다.


2

예, 다른 모듈에서 모듈의 변수를 숨 깁니다. C ++에서는 다른 파일의 불필요한 재 구축을 트리거하는 .h 파일을 변경하거나 변경하지 않을 때 사용합니다. 또한 정적을 먼저 넣습니다.

static const int foo = 42;

또한 사용에 따라 컴파일러는 저장소를 할당하지 않고 단순히 사용되는 값을 "인라인"합니다. 정적이 없으면 컴파일러는 다른 곳에서 사용되지 않고 인라인 할 수 없다고 가정 할 수 없습니다.


2

이것은 컴파일 모듈 (.cpp 파일)에서만 표시 / 접근 할 수있는 전역 상수입니다. 이 목적으로 정적을 사용하는 BTW는 더 이상 사용되지 않습니다. 익명 네임 스페이스와 열거 형을 사용하는 것이 좋습니다.

namespace
{
  enum
  {
     foo = 42
  };
}

이것은 컴파일러가 foo를 상수로 취급하지 않게하여 최적화를 방해합니다.
Nils Pipenbrinck

enums 값은 항상 일정하므로 이것이 최적화를 방해하는 방법을
알지 못합니다

아-사실 .. 내 실수. 간단한 int- 변수를 사용했다고 생각했습니다.
Nils Pipenbrinck

Roskoto, enum이 맥락에서 어떤 이점이 있는지 명확하지 않습니다 . 자세히 설명 하시겠습니까? 이러한는 enums보통 (현대 컴파일러이 필요하지 않습니다하지만 값에 어떤 공간을 할당에서 컴파일러를 방지하기 위해 사용되는 enum그것을 위해 해킹) 및 값에 대한 포인터의 생성을 방지 할 수 있습니다.
Konrad Rudolph

Konrad,이 경우 열거 형을 사용하는 데 정확히 어떤 문제가 있습니까? 열거 형은 정확한 정수가 필요한 경우에 사용됩니다.
Roskoto

1

비공개로 설정하면 여전히 헤더에 표시됩니다. 나는 작동하는 "가장 약한"방식을 사용하는 경향이 있습니다. Scott Meyers의이 고전적인 기사를 참조하십시오 : http://www.ddj.com/cpp/184401197 (기능에 관한 것이지만 여기에서도 적용 할 수 있습니다).

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