컴파일러가 코드를 위반했는지 어떻게 알 수 있습니까? 컴파일러라면 어떻게해야합니까?


14

가끔 C ++ 코드는 일정 수준의 최적화로 컴파일 할 때 작동하지 않습니다. 코드를 손상시키는 최적화를 수행하는 컴파일러 일 수도 있고 정의되지 않은 동작을 포함하는 코드 일 수도 있습니다.

더 높은 최적화 수준으로 컴파일 할 때 중단되는 코드가 있다고 가정합니다. 그것이 코드인지 컴파일러인지 어떻게 알 수 있습니까? 컴파일러라면 어떻게해야합니까?


43
아마도 당신 일 것입니다.
littleadv

9
@littleadv, gcc 및 msvc의 최신 버전조차도 버그로 가득 차 있으므로 확실하지 않습니다.
SK-logic

3
모든 경고가 활성화 되었습니까?

@ Thorbjørn Ravn Andersen : 예, 가능합니다.
sharptooth

3
FWIW : 1) 컴파일러가 엉망이 될 수있는 까다로운 일을하지 않으려 고합니다 .2) (속도를 위해) 최적화 플래그가 중요한 유일한 위치는 프로그램 카운터가 시간의 상당 부분을 소비하는 코드입니다. 타이트한 CPU 루프를 작성하지 않는 한 많은 응용 프로그램에서 PC는 기본적으로 라이브러리 또는 I / O에 모든 시간을 소비합니다. 이런 종류의 앱에서 / O 스위치는 전혀 도움이되지 않습니다.
Mike Dunlavey

답변:


19

나는 대부분의 경우 컴파일러가 아닌 코드가 깨지는 것이 안전한 내기라고 말합니다. 그리고 컴파일러 인 특별한 경우에도, 특정 컴파일러가 준비되지 않은 특이한 방식으로 모호한 언어 기능을 사용하고있을 것입니다. 즉, 코드를 더 관용적으로 변경하고 컴파일러의 약점을 피할 수 있습니다.

어쨌든, 언어 사양을 기반으로 컴파일러 버그를 발견 했다는 것을 증명할 수 있으면 컴파일러 개발자에게보고하여 시간이 지나도 수정 될 수 있도록합니다.


@ SK-logic, 충분히 공평합니다, 나는 그것을 뒷받침 할 통계가 없습니다. 그것은 내 자신의 경험을 기반으로하며, 언어 및 / 또는 컴파일러의 한계를 거의 늘리지 않았다는 것을 인정합니다. 다른 사람들이 더 자주 할 수 있습니다.
Péter Török

(1) @ SK-Logic : 방금 하나의 컴파일러에서 시도한 C ++ 컴파일러 버그, 동일한 코드를 발견하고 다른 컴파일러에서 시도했습니다.
umlcat

8
@umlcat : 지정되지 않은 동작에 따라 코드 일 가능성이 높습니다. 한 컴파일러에서는 예상과 일치하지만 다른 컴파일러에서는 그렇지 않습니다. 그렇다고 고장났다는 의미는 아닙니다.
Javier

@Ritch Melton, LTO를 사용해 본 적이 있습니까?
SK-logic

1
게임 콘솔에 대해 이야기 할 때 Crashworks에 동의합니다. 특정 상황에서 난해한 컴파일러 버그를 찾는 것은 드문 일이 아닙니다. 그래도 많이 사용되는 컴파일러를 사용하여 일반 PC를 대상으로하는 경우 이전에는 아무도 보지 못한 컴파일러 버그가 발생할 가능성이 거의 없습니다.
Trevor Powell

14

다른 버그와 마찬가지로 통제 된 실험을 수행하십시오. 의심스러운 영역을 좁히고 다른 모든 항목에 대한 최적화 기능을 끄고 해당 코드에 적용되는 최적화 기능을 변경하십시오. 100 % 재현성을 얻으면 코드 최적화를 시작하여 특정 최적화를 방해 할 수있는 것들을 소개하십시오 (예 : 가능한 포인터 별칭 지정, 잠재적 부작용이있는 외부 호출 삽입 등). 디버거에서 어셈블리 코드를 보면 도움이 될 수 있습니다.


무엇을 도울 수 있습니까? 그것이 컴파일러 버그라면-무엇?
littleadv

2
@littleadv, 만약 그것이 컴파일러 버그라면, 당신은 그것을 고치려고 시도하거나 (또는 ​​상세하게 올바르게보고 할 수도 있고) 이것을 계속 사용할 운명이라면, 나중에 그것을 피하는 방법을 찾을 수 있습니다 잠시 동안 컴파일러 버전. 수많은 C ++-ish 경계 문제 중 하나 인 자체 코드가있는 경우이 종류의 정밀 검사는 버그를 수정하고 향후 종류를 피하는 데 도움이됩니다.
SK-logic

따라서 제 대답에서 말했듯이-보고 이외의 결함에 관계없이 치료에는 큰 차이가 없습니다.
littleadv

3
@littleadv, 문제의 본질을 이해하지 못하면 반복해서 직면하게 될 것입니다. 그리고 종종 스스로 컴파일러를 고칠 수도 있습니다. 그리고, 불행히도 C ++ 컴파일러에서 버그를 찾는 것은 전혀 불가능하지 않습니다.
SK-logic

10

결과로 발생한 어셈블리 코드를 검사하고 소스에서 요구하는 작업을 수행하는지 확인하십시오. 불확실한 방식으로 코드가 잘못되었을 가능성이 매우 높습니다.


1
이것은 실제로이 질문에 대한 유일한 대답입니다. 이 경우 컴파일러 작업은 C ++에서 어셈블리 언어로 사용자를 안내하는 것입니다. 컴파일러라고 생각합니다 ... 컴파일러가 작동하는지 확인하십시오. 그렇게 간단합니다.
old_timer

7

30 년이 넘는 프로그래밍 기간 동안, 내가 찾은 진정한 컴파일러 (코드 생성) 버그의 수는 여전히 ~ 10입니다. 같은 기간에 발견하고 수정 한 내 자신 (및 다른 사람들의) 버그의 수는 아마도 > 10,000. 내 "거짓의 법칙"은 컴파일러로 인해 주어진 버그가 발생할 확률이 <0.001이라는 것입니다.


1
당신은 운이 좋다. 내 평균은 한 달에 1 건 정도의 나쁜 버그이며 사소한 경계 문제는 훨씬 더 자주 발생합니다. 그리고 사용중인 최적화 수준이 높을수록 컴파일러 오류 가능성이 높습니다. -O3 및 LTO를 사용하려는 경우 한 번에 두 개를 찾지 않으면 운이 좋을 것입니다. 그리고 여기에서는 릴리스 버전의 버그만 계산합니다. 컴파일러 개발자는 저의 작업에서 훨씬 더 많은 종류의 문제에 직면하고 있지만, 그렇지 않습니다. 컴파일러를 망치는 것이 얼마나 쉬운 지 알고 있습니다.
SK-logic

2
25 년과 나는 또한 매우 많이 보았다. 컴파일러는 매년 나 빠지고 있습니다.
old_timer

5

나는 의견을 쓰기 시작했고 너무 길고 너무 많이 결정했다.

나는 그것이 깨진 코드라고 주장합니다. 드물게 컴파일러에서 버그를 발견 한 경우 컴파일러 개발자에게 버그를보고해야하지만 그 차이가 끝납니다.

해결책은 문제가되는 구문을 식별하고 리팩터링하여 동일한 논리를 다르게 수행하는 것입니다. 버그가 당신 편이든 컴파일러이든 관계없이 문제를 해결할 것입니다.


5
  1. 코드를 엄청나게 다시 읽으십시오. ASSERT 또는 기타 디버그 (또는보다 일반적인 구성) 관련 명령문에서 부작용이있는 작업을 수행하지 않아야합니다. 또한 디버그 빌드 메모리에서 메모리 포인터가 다르게 초기화된다는 것을 기억하십시오. 여기에서 확인할 수 있습니다 : 디버깅-메모리 할당 표현 . Visual Studio 내에서 실행할 때 환경 변수로 명시 적으로 지정하지 않는 한 릴리스 힙에서도 디버그 힙을 사용합니다.
  2. 빌드를 확인하십시오. 실제 컴파일러가 아닌 다른 곳에서 복잡한 빌드로 문제를 겪는 것이 일반적입니다. 종속성은 종종 범인입니다. "완전히 다시 작성해 보셨습니까?"는 "창을 다시 설치해 보셨습니까?"라는 대답을 거의 불러 일으키지 만 종종 도움이됩니다. a) 재부팅. b) 모든 중간 및 출력 파일을 수동으로 삭제하고 다시 작성합니다.
  3. 코드를 살펴보고 정의되지 않은 동작을 일으킬 수있는 위치를 확인하십시오. C ++에서 한동안 일 해왔다면, "나는 그것을 믿을 수 없다고 확신 할 수 없다 ..." 정의되지 않은 동작인지 여부를 확인하는 코드 유형
  4. 그래도 문제가 해결되지 않으면 문제를 일으키는 파일에 대해 사전 처리 된 출력을 생성하십시오. 예기치 않은 매크로 확장은 모든 종류의 재미를 유발할 수 있습니다 (동료가 H라는 이름으로 매크로가 좋은 아이디어라고 결정한 시간이 생각납니다.) 프로젝트 구성간에 예기치 않은 변경이 있는지 사전 처리 된 출력을 검사하십시오.
  5. 최후의 수단-이제는 실제로 컴파일러 버그 랜드에 있습니다-어셈블리 출력을보십시오. 어셈블리가 실제로 수행하는 작업을 처리하기 위해 파고 싸울 수도 있지만 실제로는 매우 유익합니다. 여기에서 선택한 기술을 사용하여 미세 최적화도 평가할 수 있으므로 모든 것이 손실되지는 않습니다.

"정의되지 않은 동작"에 +1 나는 그것에 물린 적이있다. int + int마치 하드웨어 ADD 명령어로 컴파일 된 것처럼 오버 플로우 하도록 의존하는 일부 코드를 작성했습니다. 이전 버전의 GCC로 컴파일하면 제대로 작동하지만 최신 컴파일러로 컴파일하면 작동하지 않습니다. GCC의 훌륭한 사람들은 정수 오버플로의 결과가 정의되지 않았기 때문에 최적화 프로그램은 결코 일어나지 않는다는 가정하에 작동 할 수 있다고 결정했습니다. 코드에서 바로 중요한 분기를 최적화했습니다.
Solomon Slow

2

그것이 코드인지 컴파일러인지 알고 싶다면 C ++의 사양을 완벽하게 알아야합니다.

의심이 지속되면 x86 어셈블리를 완벽하게 알아야합니다.

완벽에 대해 배우는 분위기가 없다면 최적화 수준에 따라 컴파일러가 다르게 해결하는 것은 거의 확실하게 정의되지 않은 행동입니다.


(+1) @mouviciel : 사양에 있더라도 컴파일러에서 지원하는 기능인지 여부에 따라 다릅니다. gcc에 이상한 버그가 있습니다. 사양에 허용되는 "함수 포인터"를 사용하여 "일반 c 구조"를 선언하지만 일부 상황에서는 작동하지만 다른 상황에서는 작동하지 않습니다.
umlcat

1

표준 코드에서 컴파일 오류가 발생하거나 내부 컴파일 오류가 발생하면 최적화 프로그램이 잘못되었을 가능성이 큽니다. 그러나 루프를 최적화하는 컴파일러가 메소드 원인으로 인한 부작용을 잘못 잊어 버렸습니다.

당신이나 컴파일러인지 아는 방법에 대한 제안은 없습니다. 다른 컴파일러를 사용해보십시오.

어느 날 나는 그것이 내 코드인지 아닌지 궁금해하고 누군가 나에게 valgrind를 제안했다. 나는 5 ~ 10 분을 사용하여 프로그램을 실행했다. (나는 생각 valgrind --leak-check=yes myprog arg1 arg2했지만 다른 옵션을 가지고 놀았다) 즉시 문제가 발생한 한 특정 사례에서 실행되는 한 줄만 표시했다. 그런 다음 이상한 충돌, 오류 또는 이상한 동작이 없어 내 앱이 매끄럽게 실행되었습니다. valgrind 또는 이와 같은 다른 도구는 코드가 맞는지 알 수있는 좋은 방법입니다.

참고 사항 : 한때 내 앱의 성능이 왜 빨랐는지 궁금합니다. 그것은 모든 성능 문제가 한 줄에 있다는 것이 밝혀졌습니다. 나는 썼다 for(int i=0; i<strlen(sz); ++i) {. sz는 몇 메가 바이트였습니다. 어떤 이유로 컴파일러는 최적화 후에도 매번 strlen을 실행했습니다. 한 줄은 큰 문제가 될 수 있습니다. 공연에서 충돌까지


1

점점 더 일반적인 상황은 컴파일러가 표준에서 요구하지 않는 동작을 지원하는 C의 방언 용으로 작성된 코드를 깨뜨리고 이러한 방언을 대상으로하는 코드가 엄격하게 준수하는 코드보다 더 효율적이되도록 허용한다는 것입니다. 이러한 경우 대상 방언을 구현 한 컴파일러에서 100 % 신뢰할 수있는 "손상된"코드로 설명하거나 필요한 의미를 지원하지 않는 방언을 처리하는 컴파일러를 "손상된"것으로 설명하는 것은 불공평합니다. . 대신, 문제는 최적화가 활성화 된 최신 컴파일러가 처리 한 언어가 널리 사용되었던 방언에서 벗어나는 것 (그리고 여전히 최적화가 비활성화 된 일부 컴파일러 또는 최적화가 활성화 된 일부 컴파일러에 의해 처리됨)에서 비롯됩니다.

예를 들어, gcc의 표준 해석에 의해 요구되지 않는 많은 포인터 앨리어싱 패턴을 합법적 인 것으로 인식하는 방언을 위해 많은 코드가 작성되며, 이러한 패턴을 사용하여 코드를보다 쉽고 효율적으로 해석 할 수 있습니다. gcc의 C 표준 해석에서 가능할 것입니다. 이러한 코드는 gcc와 호환되지 않을 수 있지만 이것이 깨 졌다는 것을 의미하지는 않습니다. gcc는 최적화가 비활성화 된 상태에서만 지원하는 확장에 의존합니다.


음, 물론, C 표준 X + 확장 Y 및 Z 코딩에 아무것도 잘못이 만큼 이 당신에게 상당한 이점을 제공합니다, 당신은 당신이 그 짓을 알고, 당신은 그것을 철저하게 문서화. 안타깝게도 세 가지 조건이 모두 충족되지 않으므로 코드가 손상되었다고 말하는 것이 공정합니다.
중복 제거기

@Deduplicator : C89 컴파일러는 이전 버전과 호환되며 C99 등에서도 상향 호환되는 것으로 승격되었습니다. C89는 이전에 일부 플랫폼에서는 정의되었지만 다른 플랫폼에서는 정의되지 않은 동작에 대한 요구 사항은 없지만 C89 컴파일러는 C89 컴파일러가 동작을 정의 된 것으로 간주 한 플랫폼은 계속 그렇게해야합니다. 부호없는 짧은 유형을 부호로 승격시키는 이론적 근거는 표준 작성자가 표준이 명령 한 명령에 관계없이 그러한 방식으로 동작 할 것으로 예상합니다. 또한 ...
supercat

앨리어싱 규칙을 엄격하게 해석하면 호환성이 떨어지고 많은 종류의 코드를 사용할 수 없지만 약간의 조정 (예 : 크로스 타입 앨리어싱이 예상되는 일부 패턴을 식별 할 수 있음)하면 두 가지 문제가 모두 해결됩니다. . 이 규칙의 전체 목적은 컴파일러가 "비관적"앨리어싱을 가정하도록 요구하지 않았지만 "float x"로 지정된 경우 "foo"가 "foo ((int *) & x)"가 x를 수정하더라도 x를 수정할 수 있다고 가정해야합니다. 'float *'또는 'char *'유형의 포인터에
쓰지 마십시오.

0

문제가있는 부분을 격리하고 관찰 된 동작을 언어 사양에 따라 발생하는 동작과 비교하십시오. 확실히 쉽게,하지만 당신은 무엇을해야의 정보는 다음의 제품에하지 않는 것이 알고있다 (그리고 그냥 가정 ).

아마 그렇게 세심하지 않을 것입니다. 오히려 컴파일러 제조업체의 지원 포럼 / 메일 링리스트를 요청합니다. 컴파일러의 버그 인 경우 문제를 해결할 수 있습니다. 어쨌든 내 코드 일 것입니다. 예를 들어 스레딩의 메모리 가시성에 관한 언어 사양은 직관적이지 않을 수 있으며 특정 하드웨어 (!)에서 특정 최적화 플래그를 사용하는 경우에만 명확해질 수 있습니다. 일부 동작은 사양에 의해 정의되지 않을 수 있으므로 일부 컴파일러 / 일부 플래그와 함께 작동하고 다른 일부에서는 작동하지 않을 수 있습니다.


0

대부분의 코드에는 정의되지 않은 동작이 있습니다 (다른 사람들이 설명했듯이 C ++ 컴파일러가 너무 복잡하여 버그가있는 경우에도 컴파일러에 비해 코드에 버그가있을 가능성이 훨씬 높습니다 .C ++ 사양에도 디자인 버그가 있음) . 그리고 컴파일 된 실행 파일이 작동하더라도 (불운으로) UB가 여기에있을 수 있습니다.

따라서 Lattner의 블로그에서 모든 C 프로그래머가 정의되지 않은 동작에 대해 알아야 할 내용을 읽으십시오 (대부분 C ++ 11에도 적용됨).

Valgrind의의 도구, 최근 -fsanitize= 계측 옵션GCC (또는 연타 / LLVM은 ), 또한 도움이 될 것이다. 물론 모든 경고를 활성화하십시오.g++ -Wall -Wextra

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