Macht der C-standard erlauben die Zuweisung eines beliebigen Wertes zu einem Zeiger und der Inkrementierung es?
Ist das Verhalten von diesem code definiert?
#include <stdio.h>
#include <stdint.h>
int main(void)
{
void *ptr = (char *)0x01;
size_t val;
ptr = (char *)ptr + 1;
val = (size_t)(uintptr_t)ptr;
printf("%zu\n", val);
return 0;
}
Meine ich, können wir das zuweisen einer festen Zahl zu einem Zeiger und der Schrittweite, auch wenn es auf einigen zufälligen Adresse? (Ich weiß, dass kann man nicht dereferenzieren es)
- Haben Sie versuchen, zu kompilieren und laufen lassen?
- Der Zeiger-Wert ist nur ein Wert, gespeichert in einem Speicher, und Sie können rechnen mit ihm. Wenn Sie versuchen, Rücksicht, es ist dann nur der Wert wird interpretiert als ein Speicher.
- Kaum ein test von code Gültigkeit. Viel code mit undefiniertem Verhalten "kompiliert und ausgeführt wird".
- Warum würden Sie wollen, dies zu tun?
- nur weil der code kompiliert nicht unbedingt, dass es gut definiert Verhalten.
- ChristianGibbons '... und es laufen'. In Anbetracht der print-Anweisung, es sollten, drucken Sie den Wert 2. Das ist die wohldefiniertes Verhalten überprüfen, mit der rechten gibt. Wenn es druckt nichts anderes, als 2 nicht klar definiert.
- Welchen Teil von "undefiniert" ist nicht klar? Undefined bedeutet nicht, dass "unerwartete Ergebnisse". Es kann scheinen zu gut funktionieren seit Jahren. Das ist das problem.
- In Anbetracht der print-Anweisung, es sollten, drucken Sie den Wert 2. Das ist die wohldefiniertes Verhalten überprüfen, mit der rechten gibt. Ich werde direkt zum Punkt: diese Aussage von dir zeigt einen vollständigen Mangel an Verständnis von "undefiniertem Verhalten". "Ich lief, und es auch ausgegeben die Ausgabe, die ich erwartet" sicherlich schließt nicht aus, dass Undefiniertes Verhalten.
- In anderen Worten, nicht definiertes Verhalten in etwa bedeutet, dass die Spezifikation nicht angeben, was passieren soll. In der Tat, IIRC die Spezifikation definiert, dass jedes Programm mit undefinierten Verhalten kompilieren können, was der compiler will, da es nicht definiert. Gehören beide "kompiliert die perfekt zu den Anweisungen, die Sie erwartet/wanted", aber auch "kompiliert Code in code, der nur druckt 2" und auch "nicht kompiliert". Etwas zu tun, was Sie erwarten können, noch nicht definiertes Verhalten.
- Unter den Verhaltensweisen, die fallen unter die Beschreibung "undefined" Verhalten "tun genau das, was Sie erwarten, wenn Sie testen wollen, und dann tun Sie etwas ganz anderes, wenn Sie etwas tun, wichtig und/oder überzeugt sich selbst, dass dies nie die Ursache des bugs, die Sie finden".
- Es ist wirklich, obwohl. Nicht. Wenn Sie haben keine andere Wahl, Sie werden viel glücklicher sein, nicht zu tun, freche Dinge mit Ihnen, und wenn Sie nicht, können Sie wahrscheinlich abstrahieren hinter ein paar Funktionen, damit du wenigstens versuchen nicht, um herauszufinden,
((void(*)(char*))0x20000EE0)("Hello, World!")
wenn, dass wäre genauso gut gewesenputs
. Das heißt, diese Frage ist sinnvoll, akademisch, es ist gut, zu wissen, was UB/IDB. - Code mit Undefiniertem Verhalten ist einfach gebrochen. Kein "wenn"s, die "aber"s oder-nichts - falsch, gebrochen, Punkt. Es mag scheinen, zu tun, was du erwartest jetzt aber ändern-compiler oder compiler-version oder compiler-flags oder OS oder upgrade eine dynamische Bibliothek oder viele andere Dinge, und Sie können sehen plötzlich völlig anders Verhalten. Sie brach die Regeln der Sprache und nicht mehr irgendwelche Garantien auf das, was passieren wird.
- Dies wird manchmal als "Umsetzung-nicht definiert": es ist die Implementierung definiertes Verhalten, und je nach genau das, was die Implementierung definiert, die follow-up-code möglicherweise oder möglicherweise nicht UB
- Auch unter Ihnen sind "nachweislich dokumentiert fashion Merkmal der Umwelt". Die Autoren des Standard haben ausdrücklich anerkannt, dass viel von der Nützlichkeit der Sprache kommt von der Tatsache, dass Implementierungen zulassen, die nicht-portable Programme, um Dinge zu tun, die portable Programme nicht können. Sie erwarteten, dass compiler-Schreiber erkennen würden, dass eine hochwertige Umsetzung vorgesehen für low-level-Programmierung auf einer Plattform aussetzen sollte charakteristische Verhaltensweisen, die Plattform, auch wenn die Implementierungen, welche nicht bestimmt sind, geeignet zu sein für solche Zwecke brauchen nicht so.
- Die Autoren der Norm explizit zu erkennen in der Begründung an, dass eine Umsetzung könnte erfüllen die Anforderungen der Standard-und doch von so geringer Qualität, wie Sie werden im wesentlichen nutzlos. Wäre es nicht möglich, für etwas, was eine Qualität-Implementierung eignet sich für viele Zwecke ist ohne verhält sich vorhersehbar in einigen Fällen, wo die Norm enthält keine Anforderungen, und die Tatsache, dass code für einen bestimmten Zweck wird nicht arbeiten auf etwas anderes als Qualität der Implementierungen, die geeignet sind für diesen Zweck sollte wohl kaum als Mangel angesehen werden.
- Leider, die Autoren der Standard-Gedanke compiler-Schreiber erkennen würden, dass die Qualität Implementierungen vorgesehen für bestimmte Ziele und Felder der Anwendung müssen (um die Qualität Implementierungen geeignet für diese Zwecke) erwartungsgemäß Verhalten in Fällen, die über jene hinausgehen, die im Auftrag der Standard, ohne den Standard zu müssen ausdrücklich sagen, solche Dinge. Niemand hielt es für nötig, um explizit zu dokumentieren, dass Compiler Verhalten sich sinnvoll in Fällen, wenn, könnte es natürlich sinnvoll, auf den Plattformen, wo Verhalten sinnvoll wäre einfacher und leichter als etwas anderes zu tun.
- das ist toll, ein toller Kommentar zu versichern, die Programmierer in Nischen-domains, die wissen, wie Sie mit den features, die Ihre compiler-Hersteller garantiert, dass bestimmte Verhalten. Aber es ist ein unglaublich schlechtes Kommentar zu machen in einer Diskussion, wo die Leute denken, "es habe es einmal in ein künstliches Spielzeug-problem" ist der gold-standard für die Bestimmung konsistentes Verhalten.
- Das problem ist "vernünftig Verhalten" liegt im Auge des Betrachters. Zum Beispiel, ist es natürlich sinnvoll für Compiler zu emittieren effizienten code für alle sinnvollen Eingaben, und denke, dass ist das einzig sinnvolle Verhalten. Andere scheinen, es zu finden mehr sinnvoll für Compiler zu emittieren ineffizient code, wenn das ist, was erforderlich ist, um produzieren spezifische Müll Ausgänge aus Abfall-input.
- Auf jeder compiler geeignet für low-level oder Systeme, Programmierung, lvalue liest und schreibt wird das Natürliche Verhalten definiert, in Bezug auf die Umwandlung eines Musters von bits, die einen Wert eines gegebenen Typs ist, oder konvertieren Sie einen Wert eines bestimmten Typs in einem Muster von bits. Während eine Umsetzung geeignet sein können für low-level-Programmierung ohne Verhalten sich in diesem Mode in allen nur denkbaren Umständen, jemanden sucht, eine Qualität zu produzieren Implementierung geeignet für low-level-Programmierung zu unterstützen Semantik, die erzielbar wären mit einem solchen Verhalten, und sollten bei der praktischen...
- ...versuchen Sie Verhalten sich in diesem Mode in nicht-erfundene Fälle, in denen es wahrscheinlich egal ist (wenn die Kunden beschweren sich über die Fälle, wo es nicht, das ist ein ziemlich gutes Zeichen, dass die Fälle nicht egal). Ich würd Hinsicht ein compiler, der könnte sicher Unterstützung von einem leicht erkennbaren 90% von low-level-Programme, während das erreichen 80% der theoretisch erzielbaren Optimierungen wie überlegen man, die konnte nicht zuverlässig unterstützt viele low-level Programme ohne deaktivieren fast alle Optimierungen.
- Etwas weiter gefasst, die meisten Programme haben eine Allgemeine Anforderung "gegeben, Wenn gültige Eingabe Gültiger Ausgang; wenn gegeben ungültige Eingabe verzichten, der den Start von Atomraketen". Eine gute Sprache sollte ermöglichen, ein Programmierer, um erfüllen sowohl die Anforderungen als auch ohne viel mehr Aufwand, als notwendig wäre, sich zu treffen nur das erste mal. Viele "Optimierungen" dass Implementierungen zur Verbesserung der code-Effizienz, erfüllt nur die erste Voraussetzung, aber auf Kosten der Erhöhung der Menge der Arbeit, die Programmierer tun müssen, um die zweite, und in vielen Fällen die Verringerung der Effizienz solcher code.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Zuordnung:
Ist Umsetzung definiert Verhalten, denn es ist die Umwandlung eines integer zu einem pointer. Dies ist ausführlich in Abschnitt 6.3.2.3 der C-standard in Bezug auf Zeiger:
Als für die nachfolgenden Zeiger-Arithmetik:
Dies ist abhängig von ein paar Dinge.
Zunächst der aktuelle Wert der
ptr
kann eine trap-Darstellung als pro 6.3.2.3 oben. Wenn es ist, das Verhalten ist undefined.Nächste ist die Frage, ob
0x1
Punkte auf ein gültiges Objekt. Hinzufügen eines pointer und eine integer ist nur gültig, wenn sowohl die pointer Operanden und das Ergebnis zeigen Sie auf Elemente eines array-Objekt (ein einzelnes Objekt gilt als ein array der Größe 1) oder ein element nach dem array-Objekt. Dies ist ausführlich in Abschnitt 6.5.6:Auf eine hosted-Implementierung der Wert
0x1
fast sicherlich nicht nicht zeigen Sie auf ein gültiges Objekt, in welchem Falle der Zusatz undefined. Eine eingebettete Implementierung könnte jedoch die Unterstützung der Einstellung der Zeiger auf bestimmte Werte, und wenn ja, könnte es der Fall sein, dass0x1
hat in der Tat zeigen Sie auf ein gültiges Objekt. Wenn dem so ist, ist das Verhalten gut definiert, sonst ist es undefined.(char *)ptr
ist betrachtet zu sein, zeigt auf das erste element eine 1-element-array, dann würde(char *)ptr + 1
nicht zeigen, eine Vergangenheit, die das Letzte element einer 1-element-array?+1
. Aber... Es gibt p7 zu betrachten.char
Zeiger sind erlaubt, pointer-Arithmetik, wenn Sie auf einen Punkt im inneren eines Objekts, unabhängig von der Art (aus 6.3.2.3 § 7 Entwurf n1570 für C11). Auf jeden Fall ist es hier irrelevant...ptr + 1
gut definiert ist. Auf der anderen Seite, wenn die Umsetzung gibt z.B. eine Falle, dann ist es UB.(int *)0x01
definiert war durch meine fiktionale Umsetzung WeirdC auf einen gültigen char-array, möchten Sie, dass der rest genauso gültig, wenn auch nur auf WeirdC? Ich Frage, weil dies ist relevant für z.B. embedded-Entwicklung, wo Sie vielleicht auch mit Zeigern um einen bestimmten ganzzahligen Wert um Daten zu ändern.1
es ist undefiniert(char*)1
wahrscheinlich Punkte drin, dann alle arithmetischen sollte es sein, gut definiert.Nein, das Verhalten des Programms ist undefiniert. Sobald eine Undefinierte Konstrukt erreicht wird, das in einem Programm, jede zukünftige Verhalten ist nicht definiert. Paradoxerweise keine Vergangenheit Verhalten ist undefiniert zu.
Ergebnis
void *ptr = (char*)0x01;
wird durch die Implementierung festgelegt, zum Teil aufgrund der Tatsache, dass einchar
kann eine trap-Darstellung.Aber das Verhalten der folgenden Zeiger-Arithmetik in der Anweisung
ptr = (char *)ptr + 1;
ist undefined. Dies ist, weil die Zeiger-Arithmetik ist nur gültig innerhalb von arrays, einschließlich einer über das Ende des Arrays. Für diesen Zweck ist, ein Objekt, ein array der Länge eins.Ja, der code ist gut definiert als die Implementierung festgelegt. Es ist nicht undefiniert. Siehe ISO/IEC 9899:2011 [6.3.2.3]/5 und Anmerkung 67.
Die Sprache C wurde ursprünglich als ein system-Programmiersprache. Systeme Programmierung erforderlich Manipulation von memory-mapped hardware, die erfordern, dass Sie würde Zeug hart-codierte Adressen in Zeigern, manchmal Inkrementieren Sie diese Hinweise, und Lesen und schreiben von Daten von und an die resultierende Adresse. Zu diesem Zweck zuweisen und integer zu einem pointer und Manipulation, die Zeiger mithilfe von arithmetischen ist auch durch die Sprache bestimmt ist. Indem Sie durch die Implementierung festgelegt, was die Sprache ermöglicht wird, dass alle möglichen Dinge können passieren: von der klassisch-halt-und-catch-fire " zu der Erziehung eines bus-Fehler beim dereferenzieren eine ungerade Adresse.
Den Unterschied zwischen dem undefinierten Verhalten und Implementierung-definiert das Verhalten ist im Grunde ein Undefiniertes Verhalten bedeutet, "tun Sie das nicht, wir wissen nicht, was passieren wird" und implementierungsspezifische Verhalten bedeutet "es ist OK, gehen Sie vor und tun, es ist bis zu Ihnen, zu wissen, was passieren wird."
Es ist Undefiniertes Verhalten.
Vom N1570 (Hervorhebung Hinzugefügt):
Wenn der Wert eine trap-Darstellung, das Lesen, es ist Undefiniertes Verhalten:
Und
Daher, die Linie
void *ptr = (char *)0x01;
ist bereits möglicherweise nicht definiertes Verhalten, auf eine Umsetzung, wo(char*)0x01
oder(void*)(char*)0x01
ist eine trap-Darstellung. Die linke Seite ist ein lvalue-Ausdruck, der keine Charakter-Typ und liest eine trap-Darstellung.Hardware, das laden einer ungültigen Zeiger in eine Maschine zu registrieren, könnte das Programm zum Absturz bringen, so war dies eine erzwungene Bewegung durch den Normen-Ausschuss.
int *p = (int*)someValue;
würde definiert haben, Verhalten, wenn nichts versucht, den Zeiger inp
, unabhängig davon, ob der Ausdruck liefert einen gültigen Zeiger. Waren das nicht der Fall, das Konstrukt würde immer aufrufen Undefiniertes Verhalten, da es--genau wie bei UB--das Verhalten würde sein Verhalten sinnvoll auf Implementierungen, die festlegen, nützliches Verhalten, und Verhalten sich unvorhersehbar auf Implementierungen, die nicht.(char*)0x01
waren eine trap-Darstellung,ptr
im Ausdruckvoid *ptr = (char*)0x01;
ist ein lvalue-Ausdruck, der liest diesen Wert und keinen Charakter geben, erklärte UB? Wenn nicht, wird die nächste Zeile, die verwendetptr
definitiv ist. Bin ich Missverständnis?ptr
und wirft es zuuintptr_t
aufrufen würden UB, wenn(char*)1
waren eine trap-Darstellung. Die Präsenz in den Ausdruck ein lvalue, der hält, was eine trap-Darstellung, allerdings nur ruft UB in Fällen, in denen die lvalue ist ausgewertet. In der Erklärungvoid *ptr = (char*)0x01
; es ist jedoch kein lvalue, und wenn die Erklärung und speichern Sie wurden abgespalten, wie invoid *ptr; ptr = (char*)0x01;
die Anweisung nach der Deklaration enthalten würde, die lvalueptr
, aber schreiben würde, dass lvalue, ohne zu bewerten.ptr
), dessen Typ nicht ein Zeichen geben (wievoid*
) zu Lesen, eine trap-Darstellung ist schon UB. So, ein compiler würde emittieren dürfen, eine Maschinen-Instruktion zu Lesen, die ungültige bit-Muster in einer Adresse oder segment-register, auf einer CPU, wo Sie dazu führen würde, dass ein hardware-Falle. Interpretiert man die Norm anders, dann können wir immer noch vereinbaren, dass die Schrittweite wird UB in diesem Fall?intptr_t
oderuintptr_t
.struct { int x[1],y[1]; } it;
compiler kann (und für die meisten Compiler, wahrscheinlich wäre) Lagen die Dinge so, dass nachint *p = it.x+1, *q = it.y;
-, Zeiger -p
undq
würde vergleichen Sie gleich aber nicht identisch sein. Setzen Sie p oder q durch eine round-trip-Konvertierung durchuintptr_t
ergeben könnte entwederp
oderq
, entschieden im Unbestimmten Mode [da Sie im Vergleich gleich, entweder wäre zulässig]. Folglich ist nur die Operationen, die getan werden könnte, auf das Ergebnis dieser Umwandlung sein, die gültig sind, die auf beidenp
undq
.p
undq
vergleichen konnte gleich ohne nutzbaren Zugriff auf den gleichen Speicher? Ich sehe keinen Grund, die Standard sollten nicht garantieren, dass ein round-trip-Konvertierung von einem Zeiger durchuintptr_t
innerhalb der Lebensdauer des original-Zeiger Ziel unterordnen müssen einen Zeiger, der kann Zugriff auf alles, was das original konnte, aber der Standard nicht wirklich sagen, eine solche Sache.Der Standard verlangt nicht, dass Implementierungen Prozess integer-to-Zeiger Konvertierungen in eine sinnvolle Art und Weise für einen bestimmten integer-Werte, oder auch für alle möglichen ganzzahligen Werte außer Null-Zeiger-Konstanten. Das einzige, was es garantiert zu solchen Umbauten ist, das ein Programm speichert das Ergebnis der Konvertierung direkt in ein Objekt vom passenden Typ pointer und macht nichts mit, außer das prüfen der bytes des Objektes, im schlimmsten Fall, siehe Unbestimmte Werte. Während das Verhalten der Umwandlung eines integer zu einem pointer wird durch die Implementierung Festgelegt, würde nichts verbieten alle Umsetzung (egal, was es tatsächlich mit solchen Umbauten!) aus der Angabe, dass einige (oder sogar alle) der bytes der Darstellung mit Unbekannter Werte, und die Angabe, dass einige (oder sogar alle) integer-Werte können sich Verhalten, als ob Sie ein Rendite-trap-Repräsentationen.
Die einzigen Gründe, die Norm sagt überhaupt nichts über integer-to-Zeiger Konvertierungen sind:
In einigen Implementierungen das Konstrukt sinnvoll ist, und einige Programme für die Implementierungen erfordern.
Die Autoren der Norm haben, die nicht wie die Idee, ein Konstrukt, das verwendet wurde, auf einige Implementierungen darstellen würde eine constraint-Verletzung auf andere.
Wäre es gewesen, seltsam für den Standard zu beschreiben, ein Konstrukt, aber dann angeben, dass es hat nicht definiertes Verhalten in allen Fällen.
Persönlich, ich denke, die Standard haben sollte erlaubt Implementierungen zur Behandlung von integer-to-Zeiger Konvertierungen als constraint-Verletzungen, wenn Sie nicht definieren alle Situationen, in denen Sie hilfreich wäre, anstatt zu verlangen, dass die Compiler akzeptieren den Sinn der Codes, aber das war nicht der Philosophie der Zeit.
Denke ich, wäre es am einfachsten sein, einfach zu sagen, dass jede operation mit integer-to-Zeiger Konvertierungen mit etwas anderem als intptr_t oder uintptr_t empfangenen Werte von pointer-auf-integer-Konvertierungen ruft Undefiniertes Verhalten, aber beachten Sie, dass es üblich ist für die Qualität der Implementierungen vorgesehen für low-level-Programmierung zur Prozess zu Undefiniertem Verhalten "in einer Weise dokumentiert Merkmal der Umwelt". Der Standard ist nicht angegeben, wenn die Implementierungen verarbeiten sollen Programme, aufrufen von UB in die Mode sondern behandelt es als eine Qualität der Umsetzung Problem.
Wenn eine Implementierung gibt an, dass integer-to-Zeiger Konvertierungen funktionieren in einer Weise, die würde definieren Sie das Verhalten von
als äquivalent zu "char p = (char)2;", dann die Umsetzung, wie man erwarten sollte, das es so funktioniert. Auf der anderen Seite konnte die Umsetzung definieren Sie das Verhalten von integer-to-pointer-conversion, so dass auch:
würden release-Nasen-Dämonen. Auf den meisten Plattformen, einem compiler, wo arithmetische Operationen auf Zeigern produziert von integer-to-Zeiger Konvertierungen benahm sich seltsam, würde nicht gesehen werden, als eine qualitativ hochwertige Umsetzung geeignet für low-level-Programmierung. Ein Programmierer, der nicht beabsichtigt, eine beliebige andere Art von Implementierungen könnten somit erwarten, dass solche Konstrukte zu Verhalten sinnvoll auf die Compiler, die den code, der geeignet war, obwohl der Standard verlangt es nicht.
char *p = (char *)1;
nicht die Ursache UB dann die weitere Zuordnung zuq
würde nicht entweder. Die erste Stufe des Problems kommt mit der Folge der Besetzung zugewiesen wirdp
p
würde dazu führen, UB