향상된 GCC 6 최적화 프로그램이 실용적인 C ++ 코드를 깨뜨리는 이유는 무엇입니까?


148

GCC 6에는 새로운 최적화 기능이 있습니다 . this항상 null이 아니라고 가정하고이를 기반으로 최적화합니다.

값 범위 전파는 이제 C ++ 멤버 함수의 this 포인터가 널이 아닌 것으로 가정합니다. 이것은 일반적인 널 포인터 검사를 제거 하지만 부적합한 코드 기반 (예 : Qt-5, Chromium, KDevelop)도 중단 합니다. 임시 해결 방법으로 -fno-delete-null-pointer-checks를 사용할 수 있습니다. -fsanitize = undefined를 사용하여 잘못된 코드를 식별 할 수 있습니다.

변경 문서는이를 자주 사용하는 코드를 놀라게하기 때문에이를 위험이라고합니다.

이 새로운 가정이 실제 C ++ 코드를 어기는 이유는 무엇입니까? 부주의하거나 정보가없는 프로그래머가이 정의되지 않은 특정 동작에 의존하는 특정 패턴이 있습니까? if (this == NULL)너무 부자연 스럽기 때문에 글을 쓰는 사람은 상상할 수 없습니다 .


21
@Ben 희망적으로 당신은 좋은 방법으로 그것을 의미합니다. UB를 호출하지 않도록 UB가있는 코드를 다시 작성해야합니다. 그렇게 간단합니다. 대체로 FAQ를 얻는 방법이 나와 있습니다. 따라서 실제 문제는 아닙니다. 문제 없다.
Reinstate Monica

49
사람들이 코드에서 null 포인터를 역 참조하여 방어하는 것을보고 놀랐습니다. 놀랍습니다.
SergeyA

19
@Ben, 정의되지 않은 행동을 탐구하는 것은 매우 효과적인 최적화 전략이었습니다. 코드가 더 빨리 실행되도록 최적화를 좋아하기 때문에 좋아합니다.
SergeyA

17
SergeyA에 동의합니다. 전체 brouhaha는 사람들이 this암시 적 매개 변수로 전달되는 사실에 머물러 있기 때문에 시작 되었으므로 마치 명시 적 매개 변수 인 것처럼 사용하기 시작합니다. 그렇지 않습니다. 널을 역 참조 할 때 다른 널 포인터를 역 참조하는 것처럼 UB를 호출합니다. 그것이 전부입니다. nullptr을 전달 하려면 명시 적 매개 변수 DUH를 사용하십시오 . 그것은 느리지 않을 것이고, 더 어리석지 않을 것이며, 그러한 API를 가진 코드는 어쨌든 내부에 깊으므로 범위가 매우 제한적입니다. 이야기의 끝이라고 생각합니다.
Reinstate Monica

41
잘못된 코드의주기를 깨는 GCC에 대한 조언-> 잘못된 코드를 지원하는 비효율적 인 컴파일러-> 더 나쁜 코드-> 더 비효율적 인 컴파일-> ...
MM

답변:


87

왜 선의의 사람들이 수표를 먼저 작성해야하는지에 대한 답이 필요합니다.

가장 일반적인 경우는 아마도 자연스럽게 발생하는 재귀 호출의 일부인 클래스가있는 것입니다.

당신이 가지고 있다면 :

struct Node
{
    Node* left;
    Node* right;
};

C에서는 다음과 같이 작성할 수 있습니다.

void traverse_in_order(Node* n) {
    if(!n) return;
    traverse_in_order(n->left);
    process(n);
    traverse_in_order(n->right);
}

C ++에서는 이것을 멤버 함수로 만드는 것이 좋습니다.

void Node::traverse_in_order() {
    // <--- What check should be put here?
    left->traverse_in_order();
    process();
    right->traverse_in_order();
}

C ++ 초기 (표준화 이전)에는 멤버 함수가 this매개 변수가 내재 된 함수의 구문 설탕이라는 것이 강조되었습니다 . 코드는 C ++로 작성되었으며 동등한 C로 변환되어 컴파일되었습니다. this널 과 비교 하는 것이 의미가 있고 원래의 Cfront 컴파일러도 이것을 활용 한다는 명백한 예가있었습니다 . 따라서 C 배경에서 비롯된 수표의 확실한 선택은 다음과 같습니다.

if(this == nullptr) return;      

참고 : Bjarne Stroustrup은 심지어 여기에 대한 규칙 this이 수년에 걸쳐 변경 되었다고 언급합니다.

그리고 이것은 수년간 많은 컴파일러에서 작동했습니다. 표준화가 이루어 졌을 때 이것은 바뀌었다. 그리고 최근, 컴파일러는 멤버 함수 호출을 활용하기 시작 this존재가 nullptr이 조건이 항상 있다는 것을 의미 정의되지 않은 동작이,이다 false, 그리고 컴파일러는 생략 무료입니다.

즉,이 트리를 순회하려면 다음 중 하나를 수행해야합니다.

  • 전화하기 전에 모든 점검을 수행하십시오. traverse_in_order

    void Node::traverse_in_order() {
        if(left) left->traverse_in_order();
        process();
        if(right) right->traverse_in_order();
    }

    이는 또한 모든 루트 사이트에서 루트가 없는지 확인하는 것을 의미합니다.

  • 멤버 함수를 사용하지 마십시오

    즉, 이전 C 스타일 코드 (아마도 정적 메서드)를 작성하고 개체를 매개 변수로 명시 적으로 호출합니다. 예. 당신은 전화 사이트가 Node::traverse_in_order(node);아닌 글로 돌아 왔습니다 node->traverse_in_order();.

  • 표준을 준수하는 방식 으로이 특정 예제를 수정하는 가장 쉬운 방법은 실제로 a 대신 센티넬 노드를 사용하는 것입니다 nullptr.

    // static class, or global variable
    Node sentinel;
    
    void Node::traverse_in_order() {
        if(this == &sentinel) return;
        ...
    }

처음 두 가지 옵션 중 어느 것도 매력적으로 보이지 않으며 코드가 빠져 나갈 수 this == nullptr있지만 올바른 수정을 사용하는 대신 잘못된 코드를 작성했습니다 .

나는 이것이 이러한 코드베이스 중 일부가 this == nullptr그것들 을 점검 하도록 진화 한 방법이라고 추측 합니다.


6
1 == 0정의되지 않은 동작 은 어떻게 될 수 있습니까? 간단 false합니다.
Johannes Schaub-litb

11
수표 자체는 정의되지 않은 행동이 아닙니다. 항상 거짓이므로 컴파일러가 제거합니다.
SergeyA

15
흠 .. this == nullptr관용구는 정의되지 않은 동작이므로 nullptr 객체에 대해 멤버 함수를 호출했기 때문에 정의되지 않은 동작입니다. 그리고 컴파일러는 검사를 자유롭게 생략 할 수 있습니다
jtlim

6
최초의 표준 인 @Joshua는 1998 년에 출판되었습니다. 이전에 일어난 일은 모든 구현이 원하는 것이 었습니다. 암흑기.
SergeyA

26
허, 와우, 나는 인스턴스 없이 인스턴스 함수를 호출하는 데 의존하는 코드를 작성한 사람을 믿을 수 없다 . 나는 " thisnullable이 될 수 있다는 생각조차하지 않고"traverse_in_order를 호출하기 전에 모든 점검을한다 "라는 발췌문을 본능적으로 사용했을 것이다 . 아마도 이것이 내 두뇌에서 UB의 위험을 막고 이와 같은 기괴한 해킹을하지 않도록 설득하는 나이에 C ++을 배우는 이점 일 것입니다.
underscore_d

65

"실제"코드가 깨져서 정의되지 않은 동작으로 시작 되었기 때문입니다. this마이크로 최적화 이외의, 일반적으로 매우 초기의 것 이외 의 null을 사용할 이유는 없습니다 .

클래스 계층 구조 순회로 인해 포인터를 조정 하면 null이 null this이 아닌 값 으로 바뀔 수 있으므로 위험한 방법 입니다. 따라서 최소한 null로 작동하는 메소드가있는 클래스는 this기본 클래스가없는 최종 클래스이어야합니다. 어떤 클래스에서도 파생 될 수 없으며 파생 될 수 없습니다. 우리는 실용에서 추악한 해킹으로 빠르게 출발하고 있습니다 .

실질적으로 코드가 추악 할 필요는 없습니다.

struct Node
{
  Node* left;
  Node* right;
  void process();
  void traverse_in_order() {
    traverse_in_order_impl(this);
  }
private:
  static void traverse_in_order_impl(Node * n)
    if (!n) return;
    traverse_in_order_impl(n->left);
    n->process();
    traverse_in_order_impl(n->right);
  }
};

빈 트리가있는 경우 (예 : root는 nullptr 임)이 솔루션은 여전히 ​​traptr_in_order를 nullptr로 호출하여 정의되지 않은 동작에 의존합니다.

트리가 비어있는 경우 (즉, null Node* root) 정적이 아닌 메소드를 호출하지 않아야합니다. 기간. 명시 적 매개 변수로 인스턴스 포인터를 가져 오는 C와 같은 트리 코드를 사용하는 것이 좋습니다.

여기서 논쟁은 null 인스턴스 포인터에서 호출 할 수있는 객체에 비 정적 메소드를 작성 해야하는 것으로 요약됩니다. 그런 필요는 없습니다. 이러한 코드를 작성하는 C-with-objects 방식은 C ++ 세계에서 훨씬 더 훌륭합니다. 최소한 안전한 유형이기 때문입니다. 기본적으로 널 (null) this은 좁은 사용 범위를 가진 미세 최적화로 IMHO가 완벽하게 허용되지 않습니다. 공개 API는 null에 의존해서는 안됩니다 this.


18
@Ben,이 코드를 작성한 사람은 처음에 잘못되었습니다. MFC, Qt 및 Chromium과 같은 끔찍한 프로젝트의 이름을 지정하는 것은 재미 있습니다. 그들과 함께 좋은 라이딩.
SergeyA

19
@Ben, Google의 끔찍한 코딩 스타일은 저에게 잘 알려져 있습니다. 여러 사람이 Google 코드를 빛나는 예라고 믿고 있지만 Google 코드 (적어도 공개적으로 사용 가능)는 잘못 작성된 경우가 많습니다. 이것이 코딩 스타일과 가이드 라인을 다시 방문하게 할 것입니다.
SergeyA

18
@Ben 이들 장치의 Chromium을 gcc 6을 사용하여 컴파일 된 Chromium으로 소급해서 바꾸는 사람은 아무도 없습니다. Chromium을 gcc 6 및 기타 최신 컴파일러를 사용하여 컴파일하려면 먼저 수정해야합니다. 그것은 큰 일이 아닙니다. this검사는 사람이 수동으로 모든 아래로 사냥하는 것처럼 그렇게 그렇지 않은 다양한 정적 코드 분석기에 의해 선택됩니다. 패치는 아마도 몇 백 줄의 사소한 변화 일 것입니다.
Monica Reinstate Monica

8
@Ben 실질적인 의미에서 null this역 참조는 즉각적인 충돌입니다. 코드에 대해 정적 분석기를 실행하려는 사람이 없더라도 이러한 문제는 매우 빠르게 발견됩니다. C / C ++는 "사용하는 기능에 대한 비용 지불"만트라를 따릅니다. 검사를 원한다면 명시 적으로 명시해야하며 this컴파일러 this가 null이 아니라고 가정하기 때문에 너무 늦었을 때 수행 하지 않아야합니다. 그렇지 this않으면를 확인해야 하며 99.9999 %의 코드에서 그러한 확인은 시간 낭비입니다.
Reinstate Monica

10
표준이 깨 졌다고 생각하는 사람을위한 나의 충고 : 다른 언어를 사용하십시오. 정의되지 않은 동작의 가능성이없는 C ++ 유사 언어의 부족은 없습니다.
MM

35

변경 문서는이를 자주 사용하는 코드를 놀라게하기 때문에이를 위험이라고합니다.

문서는 위험하다고 부르지 않습니다. 또한 놀라운 양의 코드를 깨뜨린다고 주장하지도 않는다 . 이 정의되지 않은 동작에 의존한다고 알려져 있으며 해결 방법 옵션을 사용하지 않는 한 변경으로 인해 중단 될 수있는 몇 가지 인기있는 코드 기반을 간단히 지적합니다.

이 새로운 가정이 실제 C ++ 코드를 어기는 이유는 무엇입니까?

경우 실제 C ++ 코드가 정의되지 않은 동작에 의존하고 정의되지 않은 행동이 그것을 깰 수로 변경됩니다. UB에 의존하는 프로그램이 의도 한대로 작동하는 것처럼 보이더라도 UB를 피해야하는 이유입니다.

부주의하거나 정보가없는 프로그래머가이 정의되지 않은 특정 동작에 의존하는 특정 패턴이 있습니까?

안티 안티 패턴 이 널리 퍼져 있는지 모르겠지만, 정보가없는 프로그래머는 다음을 수행하여 프로그램 충돌을 해결할 수 있다고 생각할 수 있습니다.

if (this)
    member_variable = 42;

실제 버그가 다른 곳에서 널 포인터를 참조하고있을 때.

프로그래머가 충분한 정보를 얻지 못하면이 UB에 의존하는 고급 (안티) 패턴을 만들 수 있다고 확신합니다.

if (this == NULL)너무 부자연 스럽기 때문에 글을 쓰는 사람은 상상할 수 없습니다 .

저 할 수 있어요.


11
"실제적인 C ++ 코드가 정의되지 않은 동작에 의존하는 경우, 정의되지 않은 동작으로 변경하면 동작이 중단 될 수 있습니다. 이것이 UB를 피해야하는 이유입니다"this * 1000
underscore_d

if(this == null) PrintSomeHelpfulDebugInformationAboutHowWeGotHere(); 디버거가 쉽게 알 수없는 일련의 이벤트에 대한 읽기 쉬운 로그입니다. 작성하지 않은 코드에서 큰 데이터 세트에 갑자기 임의의 null이있을 때 어디서나 검사를하지 않고이를 디버깅하는 재미를 즐기십시오 ... 그리고 이것에 대한 UB 규칙은 나중에 C ++이 작성된 후에 만들어졌습니다. 예전에는 유효했습니다.
Stephane Hockenhull

@StephaneHockenhull 그게 다야 -fsanitize=null.
eerorika

@ user2079303 문제 : 운영 코드를 실행하는 동안 체크인 할 수없는 수준으로 프로덕션 코드가 느려지고 회사에 많은 비용이 소요됩니까? 크기가 커지고 플래시에 맞지 않습니까? Atmel을 포함한 모든 대상 플랫폼에서 작동합니까? -fsanitize=nullSPI를 사용하여 핀 # 5,6,10,11의 SD / MMC 카드에 오류를 기록 할 수 있습니까 ? 그것은 보편적 인 해결책이 아닙니다. 일부는 객체 지향 원칙을 위반하여 null 객체에 액세스한다고 주장하지만 일부 OOP 언어에는 작동 할 수있는 null 객체가 있으므로 OOP의 보편적 규칙이 아닙니다. 1/2
Stephane Hockenhull

1
... 그러한 파일과 일치하는 정규식? 예를 들어, lvalue에 두 번 액세스하면 컴파일러는 코드 사이에서 코드가 몇 가지 특정 작업을 수행하지 않는 한 액세스를 통합하여 코드가 스토리지에 액세스 할 수있는 정확한 상황을 정의하는 것보다 훨씬 쉽습니다.
supercat

25

깨진 "실제"( "버기"철자를 쓰는 재미있는 방법) 코드 중 일부는 다음과 같습니다.

void foo(X* p) {
  p->bar()->baz();
}

p->bar()때로는 널 포인터를 리턴 한다는 사실을 설명하는 것을 잊어 버렸습니다. 즉, 호출하기 위해 역 참조 baz()가 정의되지 않았 음을 의미합니다 .

깨진 모든 코드에 명시 적이 if (this == nullptr)거나 if (!p) return;검사가 포함 된 것은 아닙니다 . 어떤 경우에는 단순히 멤버 변수에 액세스하지 않은 함수이므로 정상적으로 작동하는 것처럼 보입니다 . 예를 들면 다음과 같습니다.

struct DummyImpl {
  bool valid() const { return false; }
  int m_data;
};
struct RealImpl {
  bool valid() const { return m_valid; }
  bool m_valid;
  int m_data;
};

template<typename T>
void do_something_else(T* p) {
  if (p) {
    use(p->m_data);
  }
}

template<typename T>
void func(T* p) {
  if (p->valid())
    do_something(p);
  else 
    do_something_else(p);
}

이 코드에서 전화 할 때 func<DummyImpl*>(DummyImpl*) 널 포인터로 포인터에 대한 "개념적"역 참조가 p->DummyImpl::valid()있지만 실제로 해당 멤버 함수는 false액세스하지 않고 리턴합니다 *this. 그것은 return false인라인 될 수 있으므로 실제로 포인터에 전혀 액세스 할 필요가 없습니다. 따라서 일부 컴파일러에서는 정상적으로 작동하는 것처럼 보입니다. null을 역 참조하는 segfault가없고 p->valid()false이므로 코드가 do_something_else(p)null 포인터를 확인 하는 코드를 호출 하고 아무것도 수행하지 않습니다. 충돌 또는 예기치 않은 동작이 관찰되지 않습니다.

GCC 6을 사용하면 여전히 p->valid() 하지만 컴파일러는 이제 pnull이 아닌 (그렇지 않으면 p->valid()정의되지 않은 동작) 표현식을 유추하고 해당 정보를 기록합니다. 추론 된 정보는 옵티 마이저에 의해 사용되므로, 호출 do_something_else(p)이 인라인되면 if (p)검사가 중복되지 않은 것으로 간주됩니다. 컴파일러는 그것이 null이 아니라는 것을 기억하고 코드를 다음과 같이 인라인하기 때문입니다.

template<typename T>
void func(T* p) {
  if (p->valid())
    do_something(p);
  else {
    // inlined body of do_something_else(p) with value propagation
    // optimization performed to remove null check.
    use(p->m_data);
  }
}

이것은 실제로 널 포인터를 역 참조하므로 이전에 작동했던 것으로 보이는 코드는 작동을 멈 춥니 다.

이 예제에서 버그는에 있으며 func, 먼저 null을 확인 했어야합니다 (또는 호출자가 null을 사용하여 호출 한 적이 없어야 함).

template<typename T>
void func(T* p) {
  if (p && p->valid())
    do_something(p);
  else 
    do_something_else(p);
}

기억해야 할 중요한 점은 이와 같은 대부분의 최적화는 컴파일러가 "아, 프로그래머가 널에 대해이 포인터를 테스트했다면 성가 시게하기 위해 제거 할 것"이라고 말하는 컴파일러의 경우가 아니라는 것입니다. 결과적으로 인라인 및 값 범위 전파와 같은 다양한 수준의 최적화가 결합되어 이러한 검사가 중복 검사를 수행합니다. 이는 이전 검사 또는 역 참조 이후에 발생하기 때문입니다. 컴파일러가 함수의 A 지점에서 포인터가 널이 아님을 알고 동일한 함수의 B 지점 이전에 포인터가 변경되지 않으면 B에서도 널이 아님을 알게됩니다. 점 A와 B는 실제로는 별도의 함수에 있었지만 이제는 하나의 코드로 결합 된 코드 일 수 있으며 컴파일러는 포인터가 더 많은 위치에서 널이 아님에 대한 지식을 적용 할 수 있습니다.


?와 같은 사용법이 발생할 때 컴파일 타임 경고를 출력하도록 GCC 6을 계측 할 수 this있습니까?
jotik


3
@ jotik, ^^ TC가 말한 내용입니다. 가능하지만 모든 코드에 대해 항상 경고 메시지가 표시 됩니다. 값 범위 전파는 가장 일반적인 최적화 중 하나이며 거의 모든 코드에 영향을 미칩니다. 옵티마이 저는 코드를 볼 뿐이며 단순화 할 수 있습니다. 그들은 "멍청한 UB가 최적화되면 경고를 받고 싶은 바보에 의해 작성된 코드 조각"을 보지 못한다. 컴파일러가 "프로그래머가 최적화하기를 원하는 중복 검사"와 "프로그래머가 도움이 될 것으로 생각하지만 중복되는 중복 검사"의 차이점을 쉽게 구분할 수는 없습니다.
Jonathan Wakely

1
의 잘못된 사용을 포함하여 다양한 유형의 UB에 대해 런타임 오류 를 제공하도록 코드를 계측 this하려면 다음을 사용하십시오.-fsanitize=undefined
Jonathan Wakely


-25

C ++ 표준은 중요한 방식으로 깨졌습니다. 불행하게도, GCC 개발자는 사용자를 이러한 문제로부터 보호하기보다는 한계가없는 최적화를 구현하기위한 변명으로 정의되지 않은 동작을 사용하기로 결정했습니다.

여기에 내가 설명하는 것보다 훨씬 영리한 사람이 자세히 설명되어 있습니다. (그는 C에 대해 이야기하고 있지만 상황은 동일합니다.)

왜 해로운가요?

최신 버전의 컴파일러로 이전에 작동하고 안전한 코드를 재 컴파일하면 보안 취약점이 발생할 수 있습니다 . 플래그로 새 동작을 비활성화 할 수 있지만 기존 makefile에는 해당 플래그가 설정되어 있지 않습니다. 그리고 경고가 발생하지 않기 때문에 개발자는 이전에 합리적인 행동이 바뀌 었음을 분명하지 않습니다.

이 예제에서 개발자는을 사용하여 정수 오버플로 검사를 포함 시켰습니다 assert.이 경우 유효하지 않은 길이가 제공되면 프로그램이 종료됩니다. GCC 팀은 정수 오버플로가 정의되지 않았으므로 검사를 제거 했으므로 검사를 제거 할 수 있습니다. 이로 인해 문제가 해결 된 후이 코드베이스의 실제 인스턴스가 다시 취약 해졌습니다.

모든 것을 읽으십시오. 눈물을 흘리기에 충분합니다.

그래, 이건 어때?

과거로 돌아가서 다음과 같은 일반적인 관용구가있었습니다.

 OPAQUEHANDLE ObjectType::GetHandle(){
    if(this==NULL)return DEFAULTHANDLE;
    return mHandle;

 }

 void DoThing(ObjectType* pObj){
     osfunction(pObj->GetHandle(), "BLAH");
 }

따라서 관용구는 다음과 같습니다. pObjnull이 아닌 경우 포함 된 핸들을 사용하고, 그렇지 않으면 기본 핸들을 사용합니다. 이것은 GetHandle함수에 캡슐화되어 있습니다.

트릭은 비가 상 함수를 호출해도 실제로는 this 포인터를 하지 않으므로 액세스 위반이 없습니다.

나는 아직도 그것을 얻지 못한다

그렇게 작성된 많은 코드가 존재합니다. 누군가 줄을 바꾸지 않고 단순히 그것을 다시 컴파일하면 DoThing(NULL)운이 좋으면 모든 호출 이 충돌하는 버그입니다.

운이 좋지 않으면 충돌하는 버그에 대한 호출은 원격 실행 취약점이됩니다.

이것은 심지어 자동으로 발생할 수 있습니다. 자동화 된 빌드 시스템이 있습니까? 최신 컴파일러로 업그레이드하는 것은 무해합니다. 그러나 지금은 컴파일러가 GCC가 아닌 경우가 아닙니다.

알겠습니다.

그들은 들었습니다. 그들은 결과에 대한 완전한 지식으로 이것을하고 있습니다.

하지만 ... 왜?

누가 말할 수 있습니까? 혹시:

  • 실제 코드보다 C ++ 언어의 이상적인 순도를 중요하게 생각합니다.
  • 그들은 사람들이 표준을 따르지 않기 때문에 처벌을 받아야한다고 믿는다
  • 그들은 세상의 현실을 이해하지 못한다
  • 그들은 ... 의도적으로 버그를 도입하고 있습니다. 아마도 외국 정부의 경우 일 것입니다. 어디 사세요? 모든 정부는 전 세계 대부분에 대해 외국인이며, 대부분 세계에 적대적입니다.

아니면 다른 것. 누가 말할 수 있습니까?


32
답의 모든 한 줄에 동의하지 마십시오. 엄격한 앨리어싱 최적화에 대해서도 동일한 의견이 제시되었으며 현재는 그 의견이 기각되었습니다. 해결책은 나쁜 개발 습관을 기반으로 최적화를 방지하지 않고 개발자를 교육하는 것입니다.
SergeyA

30
나는 가서 당신이 말한 것처럼 모든 것을 읽었고 실제로 울었습니다. 그러나 주로 펠릭스의 어리 석음에 당신이
Mike Vine

33
쓸모없는 rant에 대한 공감. "그들은 의도적으로 버그를 도입하고있다. 아마도 외국 정부를위한 것일 것이다." 정말? 이것은 / r / 음모가 아닙니다.
isanae

31
괜찮은 프로그래머가 반복해서 진언을 반복 하지 않으면 정의되지 않은 행동을 불러 일으키지 않지만 , 이러한 논 크는 계속해서 진행되었습니다. 그리고 무슨 일이 있었는지보세요. 나는 동정심이 없습니다. 이것이 바로 개발자의 잘못입니다. 그들은 책임을 져야합니다. 기억? 개인적인 책임? 사람들은 "에 대한하지만 당신의 진언에 의존 연습 !" 정확히이 상황이 처음에 어떻게 일어 났는가입니다. 이와 같은 넌센스를 피하는 것이 바로 표준이 처음에 존재하는 이유입니다. 표준에 따라 코드를 작성하면 문제가 없습니다. 기간.
궤도에서 가벼움 레이스

18
"새로운 버전의 컴파일러로 이전에 작동하던 안전한 코드를 재 컴파일하면 보안 취약점이 발생할 수 있습니다." 한 컴파일러의 한 버전이 나머지 영원 동안 허용되는 유일한 컴파일러라고 명령하지 않는 한. 리눅스 커널이 정확히 gcc 2.7.2.1로만 컴파일 될 수 있었을 때를 기억하십니까? gcc 프로젝트는 사람들이 황소를 먹었 기 때문에 갈등했다. 그것을 지나는 데 오랜 시간이 걸렸습니다.
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.