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 int은 externC에서 암시 적 으로 사용 된다는 점을 언급해야합니다 . 그러나 여기에있는 규칙은 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의미inline 와 inline 수 (힘) 정의는 모든 번역 단위에 표시합니다 :

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.