Dlaczego ulepszony optymalizator GCC 6 łamie praktyczny kod C ++?

148

GCC 6 ma nową funkcję optymalizatora : zakłada, że thiszawsze nie jest zerowa i optymalizuje na tej podstawie.

Propagacja zakresu wartości zakłada teraz, że ten wskaźnik funkcji składowych C ++ jest różny od null. Eliminuje to typowe sprawdzanie zerowego wskaźnika, ale także łamie niektóre niezgodne podstawy kodu (takie jak Qt-5, Chromium, KDevelop) . Jako tymczasowe obejście można użyć kontroli -fno-delete-null-pointer-pointer. Błędny kod można zidentyfikować za pomocą -fsanitize = undefined.

Dokument zmian wyraźnie określa to jako niebezpieczne, ponieważ narusza zaskakującą ilość często używanego kodu.

Dlaczego to nowe założenie miałoby złamać praktyczny kod C ++? Czy istnieją określone wzorce, w których niedbali lub niedoinformowani programiści polegają na tym konkretnym niezdefiniowanym zachowaniu? Nie wyobrażam sobie, żeby ktoś pisał, if (this == NULL)bo to takie nienaturalne.

boot4life
źródło
21
@Ben Mam nadzieję, że masz na myśli to w dobry sposób. Kod z UB powinien zostać przepisany, aby nie wywoływał UB. To takie proste. Heck, często są FAQ, które mówią ci, jak to osiągnąć. Więc nie jest to prawdziwy problem IMHO. Wszystko dobrze.
Przywróć Monikę
49
Jestem zdumiony widząc, jak ludzie bronią wyłuskiwania zerowych wskaźników w kodzie. Po prostu wspaniałe.
SergeyA
19
@Ben, eksplorowanie niezdefiniowanych zachowań było bardzo skuteczną taktyką optymalizacji przez bardzo długi czas. Uwielbiam to, bo uwielbiam optymalizacje, dzięki którym mój kod działa szybciej.
SergeyA
17
Zgadzam się z SergeyA. Cała ta gadka zaczęła się, ponieważ ludzie wydają się rozwodzić nad faktem, że thisjest on przekazywany jako niejawny parametr, więc zaczynają go używać tak, jakby był to parametr jawny. To nie jest. Kiedy wyłuskujesz wartość null this, wywołujesz UB tak, jakbyś wyłuskiwał dowolny inny wskaźnik zerowy. To wszystko. Jeśli chcesz przekazać nullptrs, użyj jawnego parametru DUH . Nie będzie wolniejszy, nie będzie trudniejszy, a kod, który ma takie API, i tak jest głęboko zakorzeniony w wewnętrznych elementach, więc ma bardzo ograniczony zakres. Myślę, że koniec historii.
Przywróć Monikę
41
Wyrazy uznania dla GCC za przerwanie cyklu złego kodu -> nieefektywny kompilator do obsługi złego kodu -> więcej złego kodu -> bardziej nieefektywna kompilacja -> ...
MM

Odpowiedzi:

87

Chyba pytanie, na które należy odpowiedzieć, dlaczego ludzie o dobrych intencjach wypisywali czeki w pierwszej kolejności.

Najczęstszym przypadkiem jest prawdopodobnie sytuacja, w której masz klasę, która jest częścią naturalnie występującego wywołania rekurencyjnego.

Gdybyś miał:

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

w C możesz napisać:

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

W C ++ dobrze jest uczynić z tego funkcję składową:

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

We wczesnych dniach C ++ (przed standaryzacją) podkreślano, że funkcje składowe były cukrem syntaktycznym dla funkcji, w której thisparametr jest niejawny. Kod został napisany w C ++, przekonwertowany na odpowiednik C i skompilowany. Były nawet wyraźne przykłady, że porównywanie thisdo wartości null było znaczące, a oryginalny kompilator Cfront również to wykorzystał. Więc wychodząc z tła C, oczywistym wyborem do sprawdzenia jest:

if(this == nullptr) return;      

Uwaga: Bjarne Stroustrup nawet wspomina, że zasady thiszostały zmienione przez lata tutaj

I to działało na wielu kompilatorach przez wiele lat. Kiedy nastąpiła standaryzacja, to się zmieniło. Niedawno kompilatory zaczęły wykorzystywać wywoływanie funkcji składowej, w której thisistnienie nullptrjest niezdefiniowanym zachowaniem, co oznacza, że ​​ten warunek jest zawsze false, a kompilator może go pominąć.

Oznacza to, że aby przejść przez to drzewo, musisz:

  • Przed zadzwonieniem wykonaj wszystkie testy traverse_in_order

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

    Oznacza to również sprawdzanie w KAŻDEJ witrynie wywołania, czy możesz mieć zerowy root.

  • Nie używaj funkcji członkowskiej

    Oznacza to, że piszesz stary kod w stylu C (być może jako metoda statyczna) i wywołujesz go z obiektem jawnie jako parametr. na przykład. wrócisz do pisania, Node::traverse_in_order(node);a nie node->traverse_in_order();do strony telefonicznej.

  • Uważam, że najłatwiejszym / najdelikatniejszym sposobem naprawienia tego konkretnego przykładu w sposób zgodny ze standardami jest faktyczne użycie węzła wartowniczego, a nie nullptr.

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

Żadna z dwóch pierwszych opcji nie wydaje się być atrakcyjna i chociaż kod mógł sobie z tym poradzić, napisali zły kod za pomocą this == nullptrzamiast używać odpowiedniej poprawki.

Zgaduję, że w ten sposób niektóre z tych baz kodu ewoluowały, aby mieć this == nullptrw sobie kontrole.

jtlim
źródło
6
Jak może 1 == 0być niezdefiniowane zachowanie? Po prostu false.
Johannes Schaub - litb
11
Samo sprawdzenie nie jest niezdefiniowanym zachowaniem. Jest po prostu zawsze fałszywa, a tym samym eliminowana przez kompilator.
SergeyA
15
Hmm .. this == nullptridiom jest niezdefiniowanym zachowaniem, ponieważ wcześniej wywołałeś funkcję składową na obiekcie nullptr, co jest niezdefiniowane. Kompilator może pominąć sprawdzanie
jtlim
6
@Joshua, pierwszy standard został opublikowany w 1998 roku. Cokolwiek wydarzyło się wcześniej, było to, czego wymagała każda implementacja. Średniowiecze.
SergeyA
26
Heh, wow, nie mogę uwierzyć, że ktokolwiek kiedykolwiek napisał kod, który polegał na wywołaniu funkcji instancji ... bez instancji . Instynktownie użyłbym fragmentu oznaczonego „Wykonaj wszystkie kontrole przed wywołaniem traverse_in_order”, nawet nie myśląc o tym this, że kiedykolwiek zostanie dopuszczony do wartości null. Myślę, że może to jest korzyść z nauki C ++ w wieku, w którym istnieje SO, aby zakorzenić niebezpieczeństwa UB w moim mózgu i odwieść mnie od robienia takich dziwacznych hacków.
underscore_d
65

Dzieje się tak, ponieważ „praktyczny” kod został uszkodzony i początkowo obejmował niezdefiniowane zachowanie. Nie ma powodu, aby używać wartości null this, poza mikro-optymalizacją, zwykle bardzo przedwczesną.

Jest to niebezpieczna praktyka, ponieważ dostosowanie wskaźników z powodu przechodzenia przez hierarchię klas może zmienić wartość zerową thisw wartość inną niż zerowa. Tak więc przynajmniej klasa, której metody mają działać z wartością null, thismusi być klasą końcową bez klasy bazowej: nie może z niczego pochodzić i nie można jej wyprowadzić. Szybko przechodzimy od praktycznego do brzydkiego hackowania .

W praktyce kod nie musi być brzydki:

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);
  }
};

Jeśli masz puste drzewo (np. Root to nullptr), to rozwiązanie nadal polega na niezdefiniowanym zachowaniu, wywołując funkcję traverse_in_order z wartością nullptr.

Jeśli drzewo jest puste, czyli null Node* root, nie powinno się na nim wywoływać żadnych metod niestatycznych. Kropka. Całkiem dobrze jest mieć kod drzewa podobny do C, który pobiera wskaźnik instancji przez jawny parametr.

Wydaje się, że ten argument sprowadza się do konieczności pisania niestatycznych metod na obiektach, które mogą być wywoływane ze wskaźnika instancji o wartości null. Nie ma takiej potrzeby. Sposób pisania takiego kodu w języku C-with-Object jest nadal o wiele przyjemniejszy w świecie C ++, ponieważ może być co najmniej bezpieczny dla typów. Zasadniczo, null thisto taka mikro-optymalizacja, z tak wąskim obszarem zastosowania, że ​​odrzucenie jej jest całkowicie w porządku. Żaden publiczny interfejs API nie powinien zależeć od wartości null this.

Przywróć Monikę
źródło
18
@Ben, ktokolwiek napisał ten kod, był w pierwszej kolejności zły. To zabawne, że nazywasz tak okropnie zepsute projekty jak MFC, Qt i Chromium. Pozbądź się ich.
SergeyA
19
@Ben, straszne style kodowania w Google są mi dobrze znane. Kod Google (przynajmniej publicznie dostępny) jest często źle napisany, mimo że wiele osób uważa, że ​​kod Google jest świetnym przykładem. Może to sprawi, że ponownie wrócą do swoich stylów kodowania (i wskazówek, gdy już to robią).
SergeyA
18
@Ben Nikt nie zastępuje wstecz Chromium na tych urządzeniach Chromium skompilowanym przy użyciu gcc 6. Zanim Chromium zostanie skompilowane przy użyciu gcc 6 i innych nowoczesnych kompilatorów, będzie wymagało naprawy. To też nie jest duże zadanie; te thiskontrole są zbierane przez różne analizatory kodu statyczne, więc to nie jest tak, jakby ktoś musi ręcznie polować je wszystkie. Łatka zawierałaby prawdopodobnie kilkaset linii trywialnych zmian.
Przywróć Monikę
8
@Ben W praktyce zerowa thisdereferencja to natychmiastowa awaria. Te problemy zostaną wykryte bardzo szybko, nawet jeśli nikomu nie zależy na uruchomieniu statycznego analizatora kodu. W języku C / C ++ obowiązuje zasada „płać tylko za funkcje, których używasz”. Jeśli chcesz sprawdzić, musisz o nich wyraźnie powiedzieć, a to oznacza, że ​​nie należy ich wykonywać this, gdy jest za późno, ponieważ kompilator zakłada, że thisnie są puste. W przeciwnym razie musiałby sprawdzić this, a dla 99,9999% kodu takie sprawdzenia są stratą czasu.
Przywróć Monikę
10
moja rada dla każdego, kto uważa, że ​​standard jest zepsuty: użyj innego języka. Nie brakuje języków podobnych do C ++, które nie mają możliwości niezdefiniowanego zachowania.
MM
35

Dokument zmian wyraźnie określa to jako niebezpieczne, ponieważ narusza zaskakującą ilość często używanego kodu.

Dokument nie nazywa tego niebezpiecznym. Nie twierdzi też, że łamie zaskakującą ilość kodu . Wskazuje po prostu kilka popularnych baz kodu, które, jak twierdzi, polegają na tym niezdefiniowanym zachowaniu i które mogłyby się zepsuć z powodu zmiany, chyba że zostanie użyta opcja obejścia.

Dlaczego to nowe założenie miałoby złamać praktyczny kod C ++?

Jeśli praktyczny kod C ++ opiera się na niezdefiniowanym zachowaniu, zmiany tego niezdefiniowanego zachowania mogą je złamać. Dlatego należy unikać UB, nawet jeśli program, na którym się ono opiera, wydaje się działać zgodnie z przeznaczeniem.

Czy istnieją określone wzorce, w których niedbali lub niedoinformowani programiści polegają na tym konkretnym niezdefiniowanym zachowaniu?

Nie wiem, czy jest to szeroko rozpowszechniony anty- wzorzec, ale niedoinformowany programista może pomyśleć, że może naprawić awarię programu, wykonując:

if (this)
    member_variable = 42;

Gdy rzeczywisty błąd usuwa odwołanie do pustego wskaźnika w innym miejscu.

Jestem pewien, że jeśli programista jest niedoinformowany, będzie mógł wymyślić bardziej zaawansowane (anty) wzorce, które opierają się na tym UB.

Nie wyobrażam sobie, żeby ktoś pisał, if (this == NULL)bo to takie nienaturalne.

Mogę.

eerorika
źródło
11
„Jeśli praktyczny kod C ++ opiera się na niezdefiniowanym zachowaniu, to zmiany tego niezdefiniowanego zachowania mogą je zepsuć. Dlatego należy unikać UB” this * 1000
underscore_d
if(this == null) PrintSomeHelpfulDebugInformationAboutHowWeGotHere(); Na przykład ładny, łatwy do odczytania dziennik sekwencji zdarzeń, o których debugger nie może łatwo powiedzieć. Baw się dobrze debugując to teraz, bez spędzania godzin na umieszczaniu sprawdzeń wszędzie, gdy w dużym zbiorze danych pojawia się nagła, losowa wartość zerowa, w kodzie, którego nie napisałeś ... A reguła UB dotycząca tego została utworzona później, po utworzeniu C ++. Kiedyś było ważne.
Stephane Hockenhull
@StephaneHockenhull Po to -fsanitize=nulljest.
eerorika
@ user2079303 Problemy: Czy to spowolni kod produkcyjny do tego stopnia, że ​​nie będzie można zostawić odprawy podczas pracy, co będzie kosztować firmę dużo pieniędzy? Czy to zwiększy rozmiar i nie zmieści się w pamięci flash? Czy to działa na wszystkich platformach docelowych, w tym Atmel? Czy można -fsanitize=nullrejestrować błędy na karcie SD / MMC na pinach # 5,6,10,11 za pomocą SPI? To nie jest uniwersalne rozwiązanie. Niektórzy argumentowali, że dostęp do obiektu zerowego jest sprzeczny z zasadami zorientowania obiektowego, ale niektóre języki OOP mają obiekt zerowy, na którym można operować, więc nie jest to uniwersalna zasada OOP. 1/2
Stephane Hockenhull
1
... wyrażenie regularne pasujące do takich plików? Mówiąc, że np. Jeśli dostęp do lvalue uzyskuje się dwukrotnie, kompilator może skonsolidować dostęp, chyba że kod między nimi wykonuje jedną z kilku konkretnych rzeczy, byłoby znacznie łatwiejsze niż próba zdefiniowania dokładnych sytuacji, w których kod ma dostęp do pamięci.
supercat
25

Niektóre z „praktycznych” (zabawnych zapisów „błędny”) kod, który został uszkodzony, wyglądały następująco:

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

i zapomniał wziąć pod uwagę fakt, że p->bar()czasami zwraca pusty wskaźnik, co oznacza, że ​​wyłuskiwanie odwołania do wywołania baz()jest niezdefiniowane.

Nie cały uszkodzony kod zawierał jawne if (this == nullptr)lub if (!p) return;kontrole. Niektóre przypadki były po prostu funkcjami, które nie miały dostępu do żadnych zmiennych składowych, więc wydawało się, że działają poprawnie. Na przykład:

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);
}

W tym kodzie, gdy wywołujesz func<DummyImpl*>(DummyImpl*)ze wskaźnikiem zerowym, występuje „koncepcyjne” wyłuskiwanie wskaźnika do wywołania p->DummyImpl::valid(), ale w rzeczywistości funkcja składowa po prostu zwraca falsebez dostępu *this. To return falsemoże być wbudowane, więc w praktyce nie ma potrzeby uzyskiwania dostępu do wskaźnika. Tak więc z niektórymi kompilatorami wydaje się, że działa OK: nie ma segfaulta dla wyłuskiwania null, p->valid()jest fałszem, więc kod wywołuje do_something_else(p), który sprawdza puste wskaźniki, więc nic nie robi. Nie zaobserwowano awarii ani nieoczekiwanego zachowania.

W GCC 6 nadal otrzymujesz wywołanie p->valid(), ale teraz kompilator wnioskuje z tego wyrażenia, które pmusi być niezerowe (w przeciwnym razie p->valid()byłoby niezdefiniowane zachowanie) i odnotowuje te informacje. Wywnioskować, że informacje te są wykorzystywane przez optymalizator tak, że jeśli wywołanie do_something_else(p)zostanie inlined The if (p)wyboru jest obecnie uważany za zbędne, ponieważ kompilator pamięta, że nie jest zerowa, a więc inlines kod do:

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);
  }
}

To teraz naprawdę wyłuskuje pusty wskaźnik, więc kod, który wcześniej wydawał się działać, przestaje działać.

W tym przykładzie występuje błąd func, który powinien był najpierw sprawdzić, czy nie ma null (lub wywołujący nie powinni byli nigdy wywołać go z null):

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

Ważną kwestią do zapamiętania jest to, że większość optymalizacji tego typu nie dotyczy kompilatora mówiącego „ach, programista przetestował ten wskaźnik pod kątem wartości null, usunę go tylko po to, aby był irytujący”. Dzieje się tak, że różne typowe optymalizacje, takie jak inlining i propagacja zakresu wartości, łączą się, aby te sprawdzenia były zbędne, ponieważ pojawiają się po wcześniejszym sprawdzeniu lub dereferencji. Jeśli kompilator wie, że wskaźnik jest różny od null w punkcie A w funkcji, a wskaźnik nie jest zmieniany przed późniejszym punktem B w tej samej funkcji, to wie, że w punkcie B również nie jest zerowy. punkty A i B mogą w rzeczywistości być fragmentami kodu, które pierwotnie znajdowały się w osobnych funkcjach, ale teraz są połączone w jeden fragment kodu, a kompilator może zastosować swoją wiedzę, że wskaźnik nie jest zerowy w wielu miejscach.

Jonathan Wakely
źródło
Czy jest możliwe, aby przyrząd GCC 6 wyprowadzał ostrzeżenia w czasie kompilacji, gdy napotka takie zastosowania this?
jotik
3
@jotik, ^^^ co powiedział TC. Byłoby to możliwe, ale otrzymywałbyś to ostrzeżenie DLA WSZYSTKICH KODÓW, CAŁY CZAS . Propagacja zakresu wartości jest jedną z najczęstszych optymalizacji, która ma wpływ na prawie cały kod, wszędzie. Optymalizatory widzą tylko kod, który można uprościć. Nie widzą „fragmentu kodu napisanego przez idiotę, który chce być ostrzeżony, jeśli ich głupi UB zostanie zoptymalizowany”. Kompilatorowi nie jest łatwo odróżnić „redundantne sprawdzenie, które programista chce zoptymalizować”, a „redundantne sprawdzenie, które według programisty pomoże, ale jest zbędne”.
Jonathan Wakely
1
Jeśli chcesz zaprogramować swój kod tak, aby wyświetlał błędy w czasie wykonywania dla różnych typów UB, w tym nieprawidłowych zastosowań this, po prostu użyj-fsanitize=undefined
Jonathan Wakely
-25

Standard C ++ został złamany na wiele ważnych sposobów. Niestety, zamiast chronić użytkowników przed tymi problemami, programiści GCC zdecydowali się użyć niezdefiniowanego zachowania jako pretekstu do wdrożenia marginalnych optymalizacji, nawet jeśli zostało im jasno wyjaśnione, jak szkodliwe jest to.

Tutaj o wiele mądrzejsza osoba, niż wyjaśniam szczegółowo. (Mówi o C, ale sytuacja jest taka sama).

Dlaczego to jest szkodliwe?

Po prostu przekompilowanie wcześniej działającego, bezpiecznego kodu z nowszą wersją kompilatora może wprowadzić luki w zabezpieczeniach . Chociaż nowe zachowanie można wyłączyć flagą, istniejące pliki makefile oczywiście nie mają ustawionej tej flagi. A ponieważ nie jest wyświetlane żadne ostrzeżenie, dla programisty nie jest oczywiste, że zmieniło się wcześniej rozsądne zachowanie.

W tym przykładzie programista uwzględnił sprawdzenie przepełnienia całkowitoliczbowego, używając polecenia assert, które zakończy działanie programu, jeśli podano nieprawidłową długość. Zespół GCC usunął sprawdzanie na podstawie tego, że przepełnienie całkowitoliczbowe jest nieokreślone, dlatego sprawdzenie można usunąć. Spowodowało to, że rzeczywiste in-the-wild instancje tej bazy kodów stały się ponownie podatne na ataki po naprawieniu problemu.

Przeczytaj całość. To wystarczy, żebyś płakał.

OK, ale co z tym?

Dawno temu istniał dość powszechny idiom, który wyglądał mniej więcej tak:

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

 }

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

Więc idiom brzmi: jeśli pObjnie jest null, używasz uchwytu, który zawiera, w przeciwnym razie używasz uchwytu domyślnego. Jest to zawarte w GetHandlefunkcji.

Sztuczka polega na tym, że wywołanie funkcji niewirtualnej w rzeczywistości nie wykorzystuje thiswskaźnika, więc nie ma naruszenia zasad dostępu.

Nadal nie rozumiem

Istnieje wiele kodu, który jest napisany w ten sposób. Jeśli ktoś po prostu przekompiluje go bez zmiany linii, każde wywołanie DoThing(NULL)jest błędem powodującym awarię - jeśli masz szczęście.

Jeśli nie masz szczęścia, wywołania błędów powodujących awarie stają się lukami w zdalnym wykonaniu.

Może to nastąpić nawet automatycznie. Masz zautomatyzowany system budowania, prawda? Aktualizacja do najnowszego kompilatora jest nieszkodliwa, prawda? Ale teraz tak nie jest - nie, jeśli twój kompilator to GCC.

OK, więc powiedz im!

Powiedziano im. Robią to z pełną świadomością konsekwencji.

ale dlaczego?

Kto może powiedzieć? Być może:

  • Cenią idealną czystość języka C ++ ponad rzeczywisty kod
  • Uważają, że ludzie powinni być karani za nieprzestrzeganie standardów
  • Nie rozumieją rzeczywistości świata
  • Oni ... celowo wprowadzają błędy. Może dla obcego rządu. Gdzie mieszkasz? Wszystkie rządy są obce w większości świata, a większość z nich jest wrogo nastawiona do części świata.

A może coś innego. Kto może powiedzieć?

Ben
źródło
32
Nie zgadzam się z każdą linią odpowiedzi. Te same komentarze dotyczą ścisłej optymalizacji aliasingu i miejmy nadzieję, że są teraz odrzucane. Rozwiązaniem jest edukacja programistów, a nie zapobieganie optymalizacjom opartym na złych nawykach programistycznych.
SergeyA
30
Poszedłem i przeczytałem wszystko tak, jak powiedziałeś, i rzeczywiście płakałem, ale głównie z powodu głupoty Felixa, o której nie sądzę, żebyś próbował ...
Mike Vine
33
Głos za bezużyteczną tyradą. - Oni… celowo wprowadzają błędy. Może dla obcego rządu. Naprawdę? To nie jest spisek / r /.
isanae
31
Przyzwoici programiści w kółko powtarzają mantrę , nie przywołując nieokreślonego zachowania , a jednak ci nonkowie i tak to zrobili. I spójrz, co się stało. Nie mam żadnego współczucia. To jest wina twórców, prosta. Muszą wziąć na siebie odpowiedzialność. Zapamietaj to? Osobista odpowiedzialność? Ludzie polegają na twojej mantrze "ale co w praktyce !" tak właśnie powstała ta sytuacja. Unikanie takich nonsensów jest właśnie powodem, dla którego istnieją standardy. Koduj według standardów, a nie będziesz miał problemu. Kropka.
Wyścigi lekkości na orbicie
18
„Po prostu przekompilowanie wcześniej działającego, bezpiecznego kodu z nowszą wersją kompilatora może wprowadzić luki w zabezpieczeniach” - tak się zawsze dzieje . Chyba że chcesz wymagać, aby jedna wersja jednego kompilatora była jedynym kompilatorem, który będzie dozwolony przez resztę wieczności. Czy pamiętasz kiedyś, kiedy jądro Linuksa mogło być skompilowane tylko z dokładnie gcc 2.7.2.1? Projekt gcc został nawet rozwidlony, ponieważ ludzie mieli dość bzdur. Minęło dużo czasu, zanim to minęło.
MM