정적 const int에 대한 정의되지 않은 참조


79

오늘 흥미로운 문제가 발생했습니다. 이 간단한 예를 고려하십시오.

template <typename T>
void foo(const T & a) { /* code */ }

// This would also fail
// void foo(const int & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

int main()
{
   Bar b;
   b.func();
}

컴파일 할 때 오류가 발생합니다.

Undefined reference to 'Bar::kConst'

자, 나는 이것이 static const int어디에도 정의되지 않았기 때문이라고 확신합니다. 이것은 내 이해에 따르면 컴파일러가 컴파일 타임에 대체를 할 수 있어야하고 정의가 필요하지 않기 때문입니다. 그러나 함수는 const int &매개 변수를 취하기 때문에 대체를하지 않고 대신 참조를 선호하는 것 같습니다. 다음과 같이 변경하여이 문제를 해결할 수 있습니다.

foo(static_cast<int>(kConst));

나는 이것이 컴파일러가 임시 int를 만들고 그에 대한 참조를 전달하도록 강요하고 있다고 믿습니다. 컴파일 타임에 성공적으로 수행 할 수 있습니다.

이것이 의도적 인 것인지 궁금하거나 gcc에서이 사건을 처리 할 수있을 것으로 너무 많이 기대하고 있습니까? 아니면 내가 어떤 이유로해서는 안되는 일인가?


1
실제로 const int kConst = 1;는 동일한 결과를 얻을 수 있습니다. 또한 함수가 유형의 매개 변수를 취하도록하는 이유 (아무것도 없다고 생각할 수 있음)가 거의 없습니다 . 여기를 const int &사용 int하십시오.
Björn Pollex 2011 년

1
@Space 실제 기능은 템플릿이었습니다. 내 질문을 수정하여 언급하겠습니다.
JaredC 2011 년

1
@Space fyi, 그것을 만들지 않으면 static`ISO C ++는 멤버 'kConst'의 초기화를 금지합니다 ... 'kConst'를 정적으로 만듭니다. '
JaredC 2011 년

죄송합니다. 수정 해 주셔서 감사합니다.
Björn Pollex 2011 년

1
성가신 일이 오류가 같은 무해한 용도에 표시 될 수 있습니다 std::min( some_val, kConst)때문에, std::min<T>형의 파라미터를 가지고 T const &, 그리고 의미는 우리가에 대한 참조를 전달해야한다는 것입니다 KCONST합니다. 최적화가 꺼져있을 때만 발생한다는 것을 알았습니다. 정적 캐스트를 사용하여 수정되었습니다.
greggo

답변:


61

의도적입니다. 9.4.2 / 4는 다음과 같이 말합니다.

정적 데이터 멤버가 const 정수 또는 const 열거 형인 경우 클래스 정의의 선언은 정수 상수 식 (5.19)이 될 상수 이니셜 라이저를 지정할 수 있습니다.이 경우 멤버는 정수 상수 식에 나타날 수 있습니다. 멤버는 프로그램에서 사용되는 경우 네임 스페이스 범위에서 정의됩니다.

const 참조로 정적 데이터 멤버를 전달할 때 3.2 / 2를 "사용"합니다.

정수 상수 표현식이 필요한 곳에 표시되거나 (5.11 참조), sizeof 연산자 (5.3.3)의 피연산자이거나, typeid 연산자의 피연산자이고 표현식이 다음의 lvalue를 지정하지 않는 경우 표현식은 잠재적으로 평가됩니다. 다형성 클래스 유형 (5.2.8). 개체 또는 오버로드되지 않은 함수는 이름이 잠재적으로 평가 된 식에 나타나는 경우 사용됩니다.

따라서 실제로 값으로 전달하거나 static_cast. 단지 GCC가 한 경우에 당신을 낚아 채게했지만 다른 경우에는 그렇지 않습니다.

[편집 : gcc는 C ++ 0x 초안의 규칙을 적용합니다. "이름이 잠재적으로 평가 된 표현식으로 나타나는 변수 또는 오버로드되지 않은 함수는 상수에 표시하기위한 요구 사항을 충족하는 객체가 아닌 한 odr 사용됩니다. 표현식 (5.19)과 lvalue에서 rvalue 로의 변환 (4.1)이 즉시 적용됩니다. " 정적 캐스트는 lvalue-rvalue 변환을 즉시 수행하므로 C ++ 0x에서는 "사용"되지 않습니다.]

const 참조의 실제 문제 foo는 인수의 주소를 가져 와서이를 글로벌에 저장된 다른 호출의 인수 주소와 비교할 수있는 권한 내에 있다는 것 입니다. 정적 데이터 멤버는 고유 한 개체이므로 foo(kConst)두 개의 다른 TU에서 호출하는 경우 전달되는 개체의 주소가 각 경우에 동일해야합니다. AFAIK GCC는 객체가 하나의 TU에 정의되어 있지 않으면이를 정렬 할 수 없습니다.

foo좋습니다. 이 경우 는 템플릿이므로 정의는 모든 TU에서 볼 수 있습니다. 따라서 아마도 컴파일러는 주소로 무엇이든 할 수있는 위험을 이론적으로 배제 할 수 있습니다. 그러나 일반적으로 존재하지 않는 객체에 대한 주소 또는 참조를 사용해서는 안됩니다 ;-)


1
참조 주소를 사용하는 예를 들어 주셔서 감사합니다. 이것이 컴파일러가 내가 기대하는 것을하지 않는 실질적인 이유라고 생각합니다.
JaredC 2011 년

완전한 적합성을 위해서는 template <int N> int intvalue() { return N; }. 그 다음으로 intvalue<kConst>, kConst오직 정수 상수 표현을 필요로하는 상황에서 나타나고, 그래서 사용되지 않는다. 그러나 함수는와 동일한 값을 가진 임시를 반환하며 kConst이는 const 참조에 바인딩 할 수 있습니다. 하지만 kConst사용되지 않는 이식성을 강화하는 더 간단한 방법이있을 수 있습니다 .
Steve Jessop 2011 년

1
r = s ? kConst1 : kConst2gcc 4.7과 함께 삼항 연산자 (예 :와 같은 ) 에서 이러한 정적 const 변수를 사용하여 동일한 문제가 발생합니다 . 나는 실제 if. 어쨌든 답변 주셔서 감사합니다!
Clodéric

2
... 그리고 std :: min / std :: max, 저를 여기로 이끌었습니다!
sage

"AFAIK GCC는 객체가 하나의 TU에 정의되어 있지 않으면이를 정렬 할 수 없습니다." .rodata에서 'weak defs'로 여러 번 컴파일 한 다음 링커가 하나만 선택하도록하여 상수로이를 수행 할 수 있기 때문에 너무 나쁩니다. 그러면 모든 실제 참조가 동일한 주소를 갖게됩니다. 이것은 실제로 typeid에 대해 수행되는 작업입니다. 그러나 공유 라이브러리를 사용하면 이상한 방식으로 실패 할 수 있습니다.
greggo

27

클래스 선언 내부에 이니셜 라이저를 사용하여 정적 const 변수를 작성하는 경우 마치 작성한 것처럼

class Bar
{
      enum { kConst = 1 };
}

GCC는 동일한 방식으로 처리합니다. 즉, 주소가 없습니다.

올바른 코드는

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;

이 예시적인 예에 ​​감사드립니다.
shuhalo

12

이것은 정말 유효한 경우입니다. 특히 때문에 같은 STL에서 기능 할 수있는 표준 : 카운트 얻어 CONST T 및 세 번째 인수로한다.

링커가 이러한 기본 코드에 문제가있는 이유를 이해하려고 많은 시간을 보냈습니다.

오류 메시지

'Bar :: kConst'에 대한 정의되지 않은 참조

링커가 기호를 찾을 수 없음을 알려줍니다.

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst

Bar :: kConst가 정의되지 않은 것을 'U'에서 볼 수 있습니다. 따라서 링커가 작업을 수행하려고 할 때 기호를 찾아야합니다. 그러나 kConst 만 선언 하고 정의하지 않습니다.

C ++의 솔루션은 다음과 같이 정의하는 것입니다.

template <typename T>
void foo(const T & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

const int Bar::kConst;       // Definition <--FIX

int main()
{
   Bar b;
   b.func();
}

그런 다음 컴파일러가 생성 된 개체 파일에 정의를 넣는 것을 볼 수 있습니다.

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst

이제 데이터 섹션에 정의되어 있다는 'R'을 볼 수 있습니다.


상수가 "nm -C"출력에 두 번 나타나는데, 처음에는 "R"과 주소와 "U"가있는 것이 괜찮습니까?
quant_dev

예가 있습니까? 제공된 예제에서 >nm -C main.o | grep kConst한 줄만 제공 0000000000400644 R Bar::kConst합니다.
Stac

나는 정적 라이브러리를 컴파일 할 때 그것을 본다.
quant_dev

1
이 경우에는 물론! 정적 라이브러리는 개체 파일의 집합 일뿐입니다. 연결은 정적 라이브러리의 클라이언트에 의해서만 수행됩니다. 아카이브 파일에 넣으면 그래서, CONST의 정의 및 호출 다른 오브젝트 파일을 바 : FUNC ()이 포함 된 오브젝트 파일, 당신은 정의에 한 번 및없이 일단 기호가 표시됩니다 : nm -C lib.a당신 제공 Constants.o: 0000000000000000 R Bar::kConstmain_file.o: U Bar::kConst ....
Stac

2

g ++ 버전 4.3.4는이 코드를 허용합니다 ( 이 링크 참조 ). 그러나 g ++ 버전 4.4.0은이를 거부합니다.


2

이 C ++의 인공물은 Bar::kConst 은 참조 할 리터럴 값이 대신 사용 .

이것은 실제로 참조 점을 만들 변수가 없음을 의미합니다.

다음을 수행해야 할 수 있습니다.

void func()
{
  int k = kConst;
  foo(k);
}

이것은 기본적으로 변경하여 달성 한 것입니다 foo(static_cast<int>(kConst));.
JaredC 2011 년

2

constexpr 멤버 함수로 바꿀 수도 있습니다.

class Bar
{
  static constexpr int kConst() { return 1; };
};

선언에 4 줄과 "constant"뒤에 중괄호가 모두 필요하므로 결국 foo = std :: numeric_limits <int> :: max () * bar :: this_is_a_constant_that_looks_like_a_method ()를 작성하고 코딩 표준이 적용되기를 바랍니다. 옵티마이 저가이를 수정합니다.
Code Abominator 2018

1

간단한 트릭 : 함수를 전달 +하기 전에 사용 kConst하십시오. 이렇게하면 상수가 참조되는 것을 방지 할 수 있으며, 이런 식으로 코드는 상수 개체에 대한 링커 요청을 생성하지 않지만 대신 컴파일러 시간 상수 값을 사용합니다.


static const선언에서 초기화 된 값 에서 주소를 가져올 때 컴파일러가 경고를 표시하지 않는 것은 유감 입니다. 이로 인해 항상 링커 오류가 발생하며 동일한 상수가 객체 파일에서 별도로 선언 될 때도 오류가됩니다. 컴파일러는 또한 상황을 완전히 인식합니다.
Ethouris

참조를 거부하는 가장 좋은 방법은 무엇입니까? 나는 현재하고있다 static_cast<decltype(kConst)>(kConst).
Velkan

@Velkan 저도 그 방법을 알고 싶습니다. tatic_cast <decltype (kConst)> (kConst) 트릭은 kConst가 char [64] 인 경우 작동하지 않습니다. "error : static_cast from 'char *'to 'decltype (start_time)'(aka 'char [64]') is not allowed".
Don Hatch

@DonHatch, 나는 소프트웨어 고고학에 관심이 없지만 내가 기억하는 한 원시 배열을 복사하여 함수에 전달하는 것은 매우 어렵습니다. 따라서 구문 적으로 foo()원래 질문의 주소가 필요하며 전체 배열의 임시 사본으로 처리하는 메커니즘이 없습니다.
Velkan

0

Cloderic (삼항 연산자의 static const :)에서 언급 한 것과 동일한 문제가 발생 r = s ? kConst1 : kConst2했지만 컴파일러 최적화 (-Os 대신 -O0)를 끈 후에 만 ​​불평했습니다. gcc-none-eabi 4.8.5에서 발생했습니다.

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