헤더 파일의 변수 선언-정적 여부?


91

일부를 리팩토링 할 때 #definesC ++ 헤더 파일에서 다음과 유사한 선언을 발견했습니다.

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

문제는 정전기가 어떤 차이를 만들까요? 고전적인 #ifndef HEADER #define HEADER #endif트릭 으로 인해 헤더를 여러 번 포함하는 것은 불가능합니다 (중요한 경우).

정적은 VAL헤더가 둘 이상의 소스 파일에 포함 된 경우 하나의 복사본 만 생성 된다는 것을 의미합니까 ?


답변:


107

이는 포함 된 각 소스 파일에 대해 static하나의 복사본이 VAL생성된다는 것을 의미합니다. 그러나 여러 포함이 VAL링크 타임에 충돌하는 여러 정의로 이어지지 않음을 의미합니다 . C에서, static당신은 하나의 소스 파일 만 정의 VAL하고 다른 소스 파일은 그것을 선언 했는지 확인해야 합니다 extern. 일반적으로 소스 파일에서 (이니셜 라이저를 사용하여) 정의 extern하고 헤더 파일에 선언을 넣어이를 수행 합니다.

static 전역 수준의 변수는 포함을 통해 얻었 든 주 파일에 있었 든 자체 소스 파일에서만 볼 수 있습니다.


편집자 주 : C ++ 에서 선언에 nor 키워드 const가없는 객체 는 암시 적으로 .staticexternstatic


나는 마지막 문장의 팬이며 매우 도움이됩니다. 42가 더 낫기 때문에 대답에 찬성하지 않았습니다. 편집 : 문법
RealDeal_EE'18 '1311.01.16

"정적은 포함 된 각 소스 파일에 대해 생성 된 VAL 사본이 하나씩 있음을 의미합니다." 두 개의 소스 파일에 헤더 파일이 포함되어 있으면 VAL의 사본이 두 개 있다는 것을 의미하는 것 같습니다. 나는 그것이 사실이 아니고 헤더를 포함하는 파일 수에 관계없이 항상 VAL의 단일 인스턴스가 있기를 바랍니다.
Brent212

4
@ Brent212 컴파일러는 선언 / 정의가 헤더 파일에서 왔는지 메인 파일에서 왔는지 알지 못합니다. 그래서 당신은 헛된 희망을 가지고 있습니다. 누군가 어리 석고 헤더 파일에 정적 정의를 넣고 두 소스에 포함 된 경우 VAL의 두 복사본이 있습니다.
Justsalt 2014-07-24

1
CONST 값은 C ++에서 내부 연결이
adrianN

112

파일 범위 변수 의 staticextern태그는 다른 번역 단위 (예 : 기타 .c또는 .cpp파일) 에서 액세스 할 수 있는지 여부를 결정합니다 .

  • static변수 내부 연결을 제공하여 다른 번역 단위에서 숨 깁니다. 그러나 내부 연결이있는 변수는 여러 번역 단위로 정의 할 수 있습니다.

  • extern변수 외부 연결을 제공하여 다른 번역 단위에서 볼 수 있도록합니다. 일반적으로 이것은 변수가 하나의 번역 단위에서만 정의되어야 함을 의미합니다.

기본값 ( static또는을 지정하지 않은 경우 extern)은 C와 C ++가 다른 영역 중 하나입니다.

  • C에서 파일 범위 변수는 extern기본적으로 (외부 연결)입니다. C를 사용하는 경우 VALis staticANOTHER_VALis extern.

  • C ++에서 파일 범위 변수는 static기본적으로 인 경우 (내부 연결) const이고 extern그렇지 않은 경우 기본적으로입니다. 당신이 사용하는 경우 C ++, 모두 VAL하고 ANOTHER_VAL있습니다 static.

C 사양 초안에서 :

6.2.2 식별자 연결 ... -5- 함수에 대한 식별자 선언에 저장 클래스 지정자가 없으면 연결은 저장 클래스 지정자 extern으로 선언 된 것처럼 정확하게 결정됩니다. 객체에 대한 식별자 선언에 파일 범위가 있고 스토리지 클래스 지정자가없는 경우 해당 링크는 외부입니다.

C ++ 사양 초안에서 :

7.1.1-스토리지 클래스 지정자 [dcl.stc] ... -6- 스토리지 클래스 지정자가없는 네임 스페이스 범위에서 선언 된 이름은 이전 선언으로 인해 내부 연결이없고 그렇지 않은 경우 외부 연결을 갖습니다. 선언 된 const. const로 선언되고 명시 적으로 선언되지 않은 extern 개체는 내부 연결이 있습니다.


47

정적은 파일 당 하나의 사본을 얻는 것을 의미하지만 다른 사람들과 달리 그렇게하는 것이 완벽하게 합법적이라고 말했습니다. 작은 코드 샘플로 쉽게 테스트 할 수 있습니다.

test.h :

static int TEST = 0;
void test();

test1.cpp :

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp :

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

이것을 실행하면 다음과 같은 출력이 제공됩니다.

0x446020
0x446040


5
예를 들어 주셔서 감사합니다!
Kyrol

궁금 TEST했다 constLTO는 하나의 메모리 위치로를 최적화 할 수있을 것입니다 경우. 그러나 -O3 -fltoGCC 8.1에서는 그렇지 않았습니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

그렇게하는 것은 불법입니다. 상수 일지라도 정적은 각 인스턴스가 컴파일 단위에 로컬임을 보장합니다. 그래도 상수로 사용하면 상수 값 자체를 인라인 할 수 있지만 주소를 사용하므로 고유 한 포인터를 반환해야합니다.
slicedlime 19.01.17

6

constC ++의 변수에는 내부 연결이 있습니다. 따라서 사용하면 static효과가 없습니다.

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

이것이 C 프로그램이라면 i(외부 연결로 인해) '다중 정의'오류가 발생합니다 .


2
글쎄요, 사용 static하는 것은 코딩이 무엇인지에 대한 의도와 인식을 깔끔하게 나타내는 효과가 있습니다. 이것은 결코 나쁜 것이 아닙니다. 나에게 이것은 virtual재정의 할 때를 포함하는 것과 같다 : 우리가 할 필요는 없지만, 우리가 할 때 상황이 훨씬 더 직관적이고 다른 선언과 일관되게 보인다.
underscore_d

C에서 다중 정의 오류 가 발생할있습니다 . 진단이 필요없는 정의되지 않은 동작입니다
MM

5

이 수준의 코드에서 정적 선언은 변수가 현재 컴파일 단위에서만 볼 수 있음을 의미합니다. 이는 해당 모듈 내의 코드 만 해당 변수를 볼 수 있음을 의미합니다.

정적 변수를 선언하는 헤더 파일이 있고 해당 헤더가 여러 C / CPP 파일에 포함 된 경우 해당 변수는 해당 모듈에 대해 "로컬"이됩니다. 헤더가 포함 된 N 개의 장소에 대해 해당 변수의 N 개의 사본이 있습니다. 그들은 서로 전혀 관련이 없습니다. 이러한 소스 파일 내의 모든 코드는 해당 모듈 내에서 선언 된 변수 만 참조합니다.

이 특별한 경우에는 'static'키워드가 어떤 이점도 제공하지 않는 것 같습니다. 나는 뭔가를 놓치고 있을지도 모르지만 그것은 중요하지 않은 것 같다. 나는 전에 이와 같은 일을 본 적이 없다.

인라인의 경우이 경우 변수가 인라인 될 가능성이 있지만 이는 const로 선언 되었기 때문입니다. 컴파일러 모듈 정적 변수를 인라인 할 가능성이 더 높지만 상황과 컴파일되는 코드에 따라 다릅니다. 컴파일러가 '정적'을 인라인 할 것이라는 보장은 없습니다.


여기서 '정적'의 장점은 헤더를 포함하는 각 모듈에 대해 하나씩 동일한 이름을 가진 여러 전역을 선언한다는 것입니다. 링커가 불평하지 않으면 그것은 혀를 깨물고 예의 바르기 때문입니다.

받는 때문에이 경우, const의는 static암시 따라서 선택 사항입니다. 결론은 Mike F가 주장한 것처럼 다중 정의 오류에 대한 민감성이 없다는 것입니다.
underscore_d


2

"정적이란 헤더가 둘 이상의 소스 파일에 포함 된 경우 VAL의 사본이 하나만 생성된다는 것을 의미합니까?"라는 질문에 답하기 위해 ...

아니 . VAL은 항상 헤더를 포함하는 모든 파일에서 별도로 정의됩니다.

이 경우 C 및 C ++ 표준이 차이를 유발합니다.

C에서 파일 범위 변수는 기본적으로 extern입니다. C를 사용하는 경우 VAL은 정적이고 ANOTHER_VAL은 extern입니다.

최신 링커는 헤더가 다른 파일에 포함 된 경우 (동일한 전역 이름이 두 번 정의 됨) ANOTHER_VAL에 대해 불평 할 수 있으며 ANOTHER_VAL이 다른 파일에서 다른 값으로 초기화되면 분명히 불평 할 것입니다.

C ++에서 파일 범위 변수는 const 인 경우 기본적으로 정적이고 그렇지 않은 경우 기본적으로 extern입니다. C ++를 사용하는 경우 VAL과 ANOTHER_VAL은 모두 정적입니다.

또한 두 변수가 모두 const로 지정된다는 사실을 고려해야합니다. 이상적으로 컴파일러는 항상 이러한 변수를 인라인하고 저장소를 포함하지 않도록 선택합니다. 스토리지를 할당 할 수있는 이유는 여러 가지가 있습니다. 내가 생각할 수있는 것들 ...

  • 디버그 옵션
  • 파일에서 가져온 주소
  • 컴파일러는 항상 저장소를 할당합니다 (복잡한 const 유형은 쉽게 인라인 될 수 없으므로 기본 유형의 특별한 경우가 됨)

참고 : 추상 기계 에는 헤더를 포함하는 개별 번역 단위마다 VAL 사본이 하나씩 있습니다. 실제로 링커는 이들을 결합하기로 결정할 수 있으며 컴파일러는 먼저 이들 중 일부 또는 전부를 최적화 할 수 있습니다.
MM

1

이러한 선언이 전역 범위 (즉, 멤버 변수가 아님)에 있다고 가정하면 다음과 같습니다.

정적 은 '내부 연결'을 의미합니다. 이 경우 const 로 선언 되었기 때문에 컴파일러에 의해 최적화 / 인라인 될 수 있습니다. const 를 생략하면 컴파일러는 각 컴파일 단위에 스토리지를 할당해야합니다.

정적 을 생략 하면 연결은 기본적으로 extern 입니다. 다시 말하지만, 당신은에 의해 저장 한 const를 컴파일러가 최적화 할 수 있습니다 / 인라인 사용 - 다움. const 를 삭제하면 링크 타임에 다중 정의 기호 오류가 발생합니다.


다른 모듈이 항상 "extern const int whatever; something (& whatever);"라고 말할 수 있기 때문에 컴파일러는 모든 경우에 const int에 대한 공간을 할당해야한다고 생각합니다.

1

정적 변수를 정의하지 않고 선언 할 수도 없습니다 (스토리지 클래스 수정 자 static 및 extern이 상호 배타적이기 때문입니다). 정적 변수는 헤더 파일에 정의 할 수 있지만 헤더 파일을 포함하는 각 소스 파일이 의도 한 것이 아닌 변수의 개인용 사본을 갖게됩니다.


"... 그러나 이로 인해 헤더 파일을 포함하는 각 소스 파일이 자체적으로 의도 한 것이 아닌 변수의 개인 사본을 갖게됩니다." - 정적 초기화 순서 실패 로 인해 각 번역 단위에 사본이 필요할 수 있습니다.
jww

1

const 변수는 기본적으로 C ++에서 정적이지만 extern C입니다. 따라서 C ++를 사용하는 경우 어떤 구성을 사용할지 알 수 없습니다.

(7.11.6 C ++ 2003 및 Apexndix C에는 샘플이 있음)

컴파일 / 링크 소스를 C 및 C ++ 프로그램으로 비교하는 예 :

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609

입니다 여전히 등의 감각 static. 그것은 프로그래머가 무엇을하고 있는지에 대한 의도 / 인식을 알리고 암시 적 .NET이없는 다른 유형의 선언 (및 fwiw, C)과 패리티를 유지합니다 static. 이는 함수를 재정의하는 선언에 포함 virtual하고 최근 override에 포함하는 것과 같습니다. 필요하지는 않지만 훨씬 더 많은 자체 문서화가 가능하며 후자의 경우 정적 분석에 도움이됩니다.
underscore_d

나는 절대적으로 동의합니다. 예를 들어 실생활에서 나는 항상 명시 적으로 작성합니다.
bruziuz

"그래서 C ++를 사용한다면 어떤 구조를 사용할지 알 수 없습니다 ..." -흠 ... const헤더의 변수에만 사용하는 프로젝트를 g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). 결과적으로 약 150 개의 곱하기 정의 된 기호가 생성되었습니다 (헤더가 포함 된 각 번역 단위에 대해 하나씩). 외부 연결을 피하기 위해 static, inline또는 익명 / 이름없는 네임 스페이스 가 필요하다고 생각합니다 .
jww

gcc-5.4를 사용하여 const int네임 스페이스 범위 및 전역 네임 스페이스에서 선언하는 baby-example을 시도했습니다 . 그리고 그것은 컴파일되고 규칙을 따릅니다. "Objects는 const로 선언되고 명시 적으로 선언되지 않은 extern은 내부 연결이 있습니다." ".... 어떤 이유로이 헤더는 규칙이 완전히 다른 C 컴파일 된 소스에 포함되어있을 수 있습니다.
bruziuz

나는 C에 대한 연결 문제와 예제 및 C ++에 대한 문제없이 업로드 @jww
bruziuz

0

Static은 다른 컴파일 유닛이 해당 변수를 외부로 내 보내지 못하도록 차단하여 컴파일러가 변수가 사용되는 곳에 변수 값을 "인라인"하고 이에 대한 메모리 저장소를 만들지 않을 수 있도록합니다.

두 번째 예제에서 컴파일러는 다른 소스 파일이이를 외부로 내 보내지 않을 것이라고 가정 할 수 없으므로 실제로 해당 값을 메모리 어딘가에 저장해야합니다.


-2

정적은 컴파일러가 여러 인스턴스를 추가하지 못하도록합니다. #ifndef 보호에서는 덜 중요하지만 헤더가 두 개의 개별 라이브러리에 포함되어 있고 애플리케이션이 링크되어 있다고 가정하면 두 개의 인스턴스가 포함됩니다.


"라이브러리"가 번역 단위 를 의미한다고 가정하면 include-guards는 동일한 번역 단위 내에 반복적으로 포함 되는 것을 막기 때문에 여러 정의를 방지하기 위해 전혀 아무것도하지 않습니다 . 그래서 그들은 static"덜 중요 하게 " 만들기 위해 아무것도하지 않습니다 . 둘 다 사용하더라도 의도하지 않은 내부적으로 연결된 여러 정의로 끝날 수 있습니다.
underscore_d
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.