2014-09-08 5 views
28

다음은 아직 무의미하다 (I는 GCC 4.7.2와 4.9.0 테스트) g++ -Wall -Wextra -Werror -Winit-self 깨끗하게 컴파일 :GCC가 정의되지 않은 동작을 루프에 넣는 것만으로 속아 넘어간 이유는 무엇입니까?

#include <iostream> 
#include <string> 

int main() 
{ 
    for (int ii = 0; ii < 1; ++ii) 
    { 
    const std::string& str = str; // !! 
    std::cout << str << std::endl; 
    } 
} 

이 라인은 아직 GCC에 의해 진단되지 않고 정의되지 않은 동작에 !! 결과를 표시했다. 왜 GCC 그렇게 쉽게 여기에 속지입니다 :

error: ‘str’ is used uninitialized in this function [-Werror=uninitialized] 

내가 알고 싶습니다

: 그러나, for 라인을 주석하는 것은 GCC는 불평 무엇입니까? 코드가 루프에 없으면 GCC가 잘못되었음을 알게됩니다. 그러나 동일한 코드를 간단한 루프에 넣으면 GCC는 더 이상 이해하지 못합니다. 이것은 우리가 컴파일러에서 C++로 바보 같은 실수를 범할 때 알리려고 많은 노력을 기울이기 때문에 괴롭 히지만, 사소한 경우에는 실패합니다.

보너스 퀴즈 : 당신이 std::string 최적화에 차례 int에와 변경하는 경우

  • , GCC는 심지어 루프 오류를 진단합니다.
  • -O3으로 깨진 코드를 작성하면 GCC는 문자 그대로 문자열 인수에 대해 null 포인터를 사용하여 ostream 삽입 함수를 호출합니다. 안전하지 않은 캐스팅을하지 않은 경우 null 참조에서 안전하다고 생각되면 다시 생각해보십시오.

나는 GCC 버그를 제출했습니다 : https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63203 - 잘못된 진단과 유사한 진단의 신뢰성에 어떤 영향을 미칠 수 있는지 더 잘 이해하고 싶습니다.

+1

Welp, +1 to clang : http://coliru.stacked-crooked.com/a/b8bde7751f3c2c0c –

+8

아마도 [GCC Bugzilla] (https://gcc.gnu.org/bugzilla)의 버그 보고서를 작성해야합니다.) ... –

+0

@BasileStarynkevitch : 절대적으로. 여기에있는 질문은 더 높은 수준의 질문입니다 : GCC가 어떻게/왜 틀린지 알고있는 코드를 받아들이 기 쉽게 속여야하는 이유는 무엇입니까? 이 점을 더 잘 이해하고 싶습니다. 처음 시도한 몇 가지 시도가 GCC를 속일 수 없었기 때문에 적절한 테스트 케이스를 작성하는 데 다소 시간이 걸렸습니다. 간단한 루프가 경고를 무시한다는 사실에 충격을 받았습니다. 그런 일이 일어나는 이유를 이해하면 코드가 깨지지 않았는지 확인하는 데 도움이 될 수 있습니다 (이전에는 내가 참조해야하는 자체 확인을 확인할 필요가 없다고 생각 했었지만 지금은 알 필요가 있음을 알았습니다). –

답변

13

나는 무엇이 잘못되었는지, 그리고 유사한 진단의 신뢰성에 어떤 영향을 줄 수 있는지에 대해 더 잘 이해하고 싶습니다.

연타 달리

, GCC는 자체 초기화 참조를 감지하는 논리를 가지고, 그래서 여기에 경고를 얻는 것은 매우 변덕과 신뢰성이 초기화되지 않은 변수의 사용을 검출하는 코드에 의존하지 않습니다 (토론 Better Uninitialized Warnings 참조) . int

컴파일러는 스트림에 초기화되지 않은 int 쓰기,하지만 std::string에있다 추상화 분명히 너무 많은 레이어를 입력 std::string의 표현에 포함 점점 const char* 사이에, 그리고 GCC는 실패 알아낼 수 문제를 감지 할 수 있습니다.

GCC는 당신이 어떤 최적화를 가능하게 한, 선언과 변수의 사용 사이 적은 코드로 간단한 예를 들어 경고를 준다 : I 진단없는 이런 종류의 의심

extern "C" int printf(const char*, ...); 

struct string { 
    string() : data(99) { } 
    int data; 
    void print() const { printf("%d\n", data); } 
}; 

int main() 
{ 
    for (int ii = 0; ii < 1; ++ii) 
    { 
    const string& str = str; // !! 
    str.print(); 
    } 
} 

d.cc: In function ‘int main()’: 
d.cc:6:43: warning: ‘str’ is used uninitialized in this function [-Wuninitialized] 
    void print() const { printf("%d\n", data); } 
             ^
d.cc:13:19: note: ‘str’ was declared here 
    const string& str = str; // !! 
       ^

을 소수에 영향을 미칠 유일한 가능성 결함을 발견하기 위해 휴리스틱에 의존하는 진단 기능. 이것들은 "may 초기화되지 않은"또는 " 엄격한 앨리어싱 규칙 위반"이라는 형식의 경고를 줄 것이며, 아마도 "배열 첨자는 배열 경계 위에 있습니다"라는 경고가됩니다. 이러한 경고는 100 % 정확하지 않으며 루프 (!)와 같은 "복잡한"논리로 인해 컴파일러가 코드 분석을 포기하고 진단을 내리지 못할 수 있습니다.

IMHO 해결책은 초기화 시점에서 자체 초기화 된 참조를 추가하는 것이고 나중에 감지되면 사용되지 않을 때 초기화되지 않은 것으로 간주하지 않을 것입니다.

1

당신은 그것이 정의되지 않은 행동이라고 주장하지만 어셈블리로 두 경우를 컴파일 할 때 스택에서 초기화되지 않는 함수 범위의 변수와 NULL로 설정된 블록 범위 변수를 확실히 볼 수 있습니다.

당신이 나에게서 얻는 것만 큼 대답이 많습니다. 나는 이것을 확실하게 해결하기 위해 C++ 스펙을 다운로드했지만, 내가 깨닫지 못했을 때 Lovecraftian 타입 푸가에 떨어졌다. ...

나는 블록 스코핑 된 케이스가 실제로 정의되지 않았다고 생각한다.

+2

null 참조를 사용하는 것은 정의되지 않은 동작입니다 .UB가 발생하는 두 줄 중 어느 것이 논쟁의 여지가 있을지 모르겠지만 분명히 UB라고 생각합니다 (세그 폴트가 발생합니다). –

+0

미안하지만, 초기화되지 않았습니다. , -Werror-uninitialized 경고.) – BadZen

+0

글쎄, 초기화가 아닌 것에 대해서는 아무런 언급도하지 않았다. GCC가 루프를 주석 처리했을 때, 초기화되지 않았거나 자체 진단으로 초기화되었는지는 문제가되지 않는다. 나, 나는 단지 진단을 기대한다! –