web-dev-qa-db-de.com

Ist das Verhalten eines Programms, das auf einem nicht erreichbaren Pfad undefiniertes Verhalten aufweist, definiert?

Erwägen

void swap(int* a, int* b)
{
    if (a != b){
        *a = *a ^ *b;
        *b = *a ^ *b;
        *a = *a ^ *b;
    }   
}

int main()
{
    int a = 0;
    int b = 1;
    swap(&a, &b); // after this b is 0 and a is 1
    return a > b ? 0 : a / b;
}

swap ist ein Versuch, den Compiler dazu zu bringen, das Programm nicht zu optimieren.

Ist das Verhalten dieses Programms definiert? a / b ist nie erreichbar, aber wenn dies der Fall wäre, würden Sie eine Division durch Null erhalten.

24
Bathsheba

Es ist nicht notwendig, einen Standpunkt zu dieser Frage auf die Nützlichkeit eines bestimmten Code-Konstrukts oder einer Code-Praxis zu stützen, noch auf irgendetwas, das über C++ geschrieben wurde, ob in seinem Standard oder in einer anderen SO Antwort, egal wie ähnlich C++ ist Definitionen können sein. Die wichtigste Sache, die zu berücksichtigen ist, ist C's Definition von undefiniertem Verhalten :

verhalten, bei Verwendung eines nicht portablen oder fehlerhaften Programmkonstrukts oder von fehlerhaften Daten, für die diese Internationale Norm keine .__ auferlegt. Anforderungen

(C2011, 3.4.3/1; Hervorhebung hinzugefügt)

Somit wird undefiniertes Verhalten zeitlich ausgelöst ("bei der Verwendung" eines Konstrukts oder von Daten), nicht durch bloße Anwesenheit.* Es ist zweckmäßig, dass dies für undefiniertes Verhalten von Daten und für Programmkonstrukte konsistent ist. der Standard muss dort nicht konsistent gewesen sein. Und wie eine andere Antwort beschreibt, ist diese Definition bei der Verwendung eine gute Entwurfsentscheidung, da Programme das undefinierte Verhalten vermeiden können, das mit fehlerhaften Daten verbunden ist.

Wenn dagegen ein Programm do undefiniertes Verhalten ausführt, folgt aus der Definition des Standards, dass das gesamte Verhalten des Programms undefiniert ist. Diese konsequente Undefiniertheit ist eine allgemeinere Art, die sich aus der Tatsache ergibt, dass die UB, die direkt mit den fehlerhaften Daten oder dem fehlerhaften Konstrukt in Verbindung steht, im Prinzip das Verhalten anderer Teile des Programms selbst nachträglich (oder scheinbar) ändern könnte. Es gibt natürlich außersprachliche Einschränkungen in Bezug auf das, was passieren könnte - also werden Nasendämonen keine Erscheinungen machen - aber diese sind nicht notwendigerweise so stark, wie man vermuten könnte.


* Vorbehalt: Einige Programmkonstrukte sind zum Übersetzungszeitpunkt used. Diese erzeugen UB in der Programmübersetzung, mit dem Ergebnis, dass jede Ausführung des Programms völlig undefiniertes Verhalten aufweist. Für ein etwas dummes Beispiel ist das Verhalten des Programms völlig undefiniert (siehe C2011, 5.1.1.2/1 , Punkt 2), wenn Ihre Programmquelle nicht mit einem nicht verlaufenden Zeilenumbruch endet.

26
John Bollinger

Das Verhalten eines Ausdrucks, der nicht ausgewertet wird, ist für das Verhalten eines Programms irrelevant. Verhalten, das bei einer Auswertung des Ausdrucks undefiniert wäre, hat keinen Einfluss auf das Verhalten des Programms.

Wenn dies der Fall wäre, wäre dieser Code nutzlos:

if (p != NULL)
    …; // Use pointer p.

(Ihre XORs könnten ein undefiniertes Verhalten aufweisen, da sie eine Trap-Darstellung erzeugen können. Sie können die Optimierung für wissenschaftliche Beispiele wie diese verhindern, indem Sie ein Objekt als flüchtig deklarieren. Wenn ein Objekt flüchtig ist, kann die C-Implementierung nicht wissen, ob sich der Wert aufgrund dessen ändern kann auf externe Mittel, also erfordert jede Verwendung des Objekts, dass die Implementierung ihren Wert liest.)

22

Im Allgemeinen darf Code, der bei der Ausführung Undefined Behavior aufrufen würde, keine Auswirkungen haben, wenn er nicht ausgeführt wird. Es gibt jedoch einige Fälle, in denen Real-Real-Implementierungen sich in entgegengesetzter Weise verhalten und die Erzeugung von Code ablehnen können, der zwar keine Einschränkungsverletzung darstellt, jedoch möglicherweise nicht in definiertem Verhalten ausgeführt werden kann.

extern struct foo z;

int main(int argc, char **argv)
{
    if (argc > 2) z;
    return 0;
}

Durch meine Lektüre des Standards werden Lvalue-Konvertierungen für unvollständige Typen explizit als Aufrufen von Undefined Behavior bezeichnet (unter anderem ist unklar, was eine Implementierung Code für ein solches Objekt erzeugen kann), so dass der Standard keine Verhaltensanforderungen auferlegt, wenn argc ist 3 oder mehr. Ich kann keine Einschränkung im Standard identifizieren, gegen die der obige Code verstoßen würde, oder ein Grundverhalten sollte nicht vollständig definiert werden, wenn argc 2 oder weniger ist. Trotzdem lehnen viele Compiler, einschließlich gcc und clang, den obigen Code vollständig ab.

0
supercat