가끔 C ++ 코드는 일정 수준의 최적화로 컴파일 할 때 작동하지 않습니다. 코드를 손상시키는 최적화를 수행하는 컴파일러 일 수도 있고 정의되지 않은 동작을 포함하는 코드 일 수도 있습니다.
더 높은 최적화 수준으로 컴파일 할 때 중단되는 코드가 있다고 가정합니다. 그것이 코드인지 컴파일러인지 어떻게 알 수 있습니까? 컴파일러라면 어떻게해야합니까?
가끔 C ++ 코드는 일정 수준의 최적화로 컴파일 할 때 작동하지 않습니다. 코드를 손상시키는 최적화를 수행하는 컴파일러 일 수도 있고 정의되지 않은 동작을 포함하는 코드 일 수도 있습니다.
더 높은 최적화 수준으로 컴파일 할 때 중단되는 코드가 있다고 가정합니다. 그것이 코드인지 컴파일러인지 어떻게 알 수 있습니까? 컴파일러라면 어떻게해야합니까?
답변:
나는 대부분의 경우 컴파일러가 아닌 코드가 깨지는 것이 안전한 내기라고 말합니다. 그리고 컴파일러 인 특별한 경우에도, 특정 컴파일러가 준비되지 않은 특이한 방식으로 모호한 언어 기능을 사용하고있을 것입니다. 즉, 코드를 더 관용적으로 변경하고 컴파일러의 약점을 피할 수 있습니다.
어쨌든, 언어 사양을 기반으로 컴파일러 버그를 발견 했다는 것을 증명할 수 있으면 컴파일러 개발자에게보고하여 시간이 지나도 수정 될 수 있도록합니다.
다른 버그와 마찬가지로 통제 된 실험을 수행하십시오. 의심스러운 영역을 좁히고 다른 모든 항목에 대한 최적화 기능을 끄고 해당 코드에 적용되는 최적화 기능을 변경하십시오. 100 % 재현성을 얻으면 코드 최적화를 시작하여 특정 최적화를 방해 할 수있는 것들을 소개하십시오 (예 : 가능한 포인터 별칭 지정, 잠재적 부작용이있는 외부 호출 삽입 등). 디버거에서 어셈블리 코드를 보면 도움이 될 수 있습니다.
30 년이 넘는 프로그래밍 기간 동안, 내가 찾은 진정한 컴파일러 (코드 생성) 버그의 수는 여전히 ~ 10입니다. 같은 기간에 발견하고 수정 한 내 자신 (및 다른 사람들의) 버그의 수는 아마도 > 10,000. 내 "거짓의 법칙"은 컴파일러로 인해 주어진 버그가 발생할 확률이 <0.001이라는 것입니다.
int + int
마치 하드웨어 ADD 명령어로 컴파일 된 것처럼 오버 플로우 하도록 의존하는 일부 코드를 작성했습니다. 이전 버전의 GCC로 컴파일하면 제대로 작동하지만 최신 컴파일러로 컴파일하면 작동하지 않습니다. GCC의 훌륭한 사람들은 정수 오버플로의 결과가 정의되지 않았기 때문에 최적화 프로그램은 결코 일어나지 않는다는 가정하에 작동 할 수 있다고 결정했습니다. 코드에서 바로 중요한 분기를 최적화했습니다.
그것이 코드인지 컴파일러인지 알고 싶다면 C ++의 사양을 완벽하게 알아야합니다.
의심이 지속되면 x86 어셈블리를 완벽하게 알아야합니다.
완벽에 대해 배우는 분위기가 없다면 최적화 수준에 따라 컴파일러가 다르게 해결하는 것은 거의 확실하게 정의되지 않은 행동입니다.
표준 코드에서 컴파일 오류가 발생하거나 내부 컴파일 오류가 발생하면 최적화 프로그램이 잘못되었을 가능성이 큽니다. 그러나 루프를 최적화하는 컴파일러가 메소드 원인으로 인한 부작용을 잘못 잊어 버렸습니다.
당신이나 컴파일러인지 아는 방법에 대한 제안은 없습니다. 다른 컴파일러를 사용해보십시오.
어느 날 나는 그것이 내 코드인지 아닌지 궁금해하고 누군가 나에게 valgrind를 제안했다. 나는 5 ~ 10 분을 사용하여 프로그램을 실행했다. (나는 생각 valgrind --leak-check=yes myprog arg1 arg2
했지만 다른 옵션을 가지고 놀았다) 즉시 문제가 발생한 한 특정 사례에서 실행되는 한 줄만 표시했다. 그런 다음 이상한 충돌, 오류 또는 이상한 동작이 없어 내 앱이 매끄럽게 실행되었습니다. valgrind 또는 이와 같은 다른 도구는 코드가 맞는지 알 수있는 좋은 방법입니다.
참고 사항 : 한때 내 앱의 성능이 왜 빨랐는지 궁금합니다. 그것은 모든 성능 문제가 한 줄에 있다는 것이 밝혀졌습니다. 나는 썼다 for(int i=0; i<strlen(sz); ++i) {
. sz는 몇 메가 바이트였습니다. 어떤 이유로 컴파일러는 최적화 후에도 매번 strlen을 실행했습니다. 한 줄은 큰 문제가 될 수 있습니다. 공연에서 충돌까지
점점 더 일반적인 상황은 컴파일러가 표준에서 요구하지 않는 동작을 지원하는 C의 방언 용으로 작성된 코드를 깨뜨리고 이러한 방언을 대상으로하는 코드가 엄격하게 준수하는 코드보다 더 효율적이되도록 허용한다는 것입니다. 이러한 경우 대상 방언을 구현 한 컴파일러에서 100 % 신뢰할 수있는 "손상된"코드로 설명하거나 필요한 의미를 지원하지 않는 방언을 처리하는 컴파일러를 "손상된"것으로 설명하는 것은 불공평합니다. . 대신, 문제는 최적화가 활성화 된 최신 컴파일러가 처리 한 언어가 널리 사용되었던 방언에서 벗어나는 것 (그리고 여전히 최적화가 비활성화 된 일부 컴파일러 또는 최적화가 활성화 된 일부 컴파일러에 의해 처리됨)에서 비롯됩니다.
예를 들어, gcc의 표준 해석에 의해 요구되지 않는 많은 포인터 앨리어싱 패턴을 합법적 인 것으로 인식하는 방언을 위해 많은 코드가 작성되며, 이러한 패턴을 사용하여 코드를보다 쉽고 효율적으로 해석 할 수 있습니다. gcc의 C 표준 해석에서 가능할 것입니다. 이러한 코드는 gcc와 호환되지 않을 수 있지만 이것이 깨 졌다는 것을 의미하지는 않습니다. gcc는 최적화가 비활성화 된 상태에서만 지원하는 확장에 의존합니다.
문제가있는 부분을 격리하고 관찰 된 동작을 언어 사양에 따라 발생하는 동작과 비교하십시오. 확실히 쉽게,하지만 당신은 무엇을해야의 정보는 다음의 제품에하지 않는 것이 알고있다 (그리고 그냥 가정 ).
아마 그렇게 세심하지 않을 것입니다. 오히려 컴파일러 제조업체의 지원 포럼 / 메일 링리스트를 요청합니다. 컴파일러의 버그 인 경우 문제를 해결할 수 있습니다. 어쨌든 내 코드 일 것입니다. 예를 들어 스레딩의 메모리 가시성에 관한 언어 사양은 직관적이지 않을 수 있으며 특정 하드웨어 (!)에서 특정 최적화 플래그를 사용하는 경우에만 명확해질 수 있습니다. 일부 동작은 사양에 의해 정의되지 않을 수 있으므로 일부 컴파일러 / 일부 플래그와 함께 작동하고 다른 일부에서는 작동하지 않을 수 있습니다.
대부분의 코드에는 정의되지 않은 동작이 있습니다 (다른 사람들이 설명했듯이 C ++ 컴파일러가 너무 복잡하여 버그가있는 경우에도 컴파일러에 비해 코드에 버그가있을 가능성이 훨씬 높습니다 .C ++ 사양에도 디자인 버그가 있음) . 그리고 컴파일 된 실행 파일이 작동하더라도 (불운으로) UB가 여기에있을 수 있습니다.
따라서 Lattner의 블로그에서 모든 C 프로그래머가 정의되지 않은 동작에 대해 알아야 할 내용을 읽으십시오 (대부분 C ++ 11에도 적용됨).
Valgrind의의 도구, 최근 -fsanitize=
계측 옵션 에 GCC (또는 연타 / LLVM은 ), 또한 도움이 될 것이다. 물론 모든 경고를 활성화하십시오.g++ -Wall -Wextra