Ist die Differenz zweier nicht ungültiger Zeigervariablen definiert (gemäß C99 und / oder C ++ 98), wenn beide NULL
bewertet werden?
Angenommen, ich habe eine Pufferstruktur, die folgendermaßen aussieht:
struct buf {
char *buf;
char *pwrite;
char *pread;
} ex;
Sprich ex.buf
auf ein Array oder einen malloc'ed Speicher. Wenn mein Code dies immer sicherstellt pwrite
und pread
innerhalb dieses Arrays oder eines nach ihm zeigt, bin ich ziemlich sicher, dass dies ex.pwrite - ex.pread
immer definiert wird. Was ist jedoch, wenn pwrite
und pread
beide NULL sind? Kann ich nur erwarten, dass das Subtrahieren der beiden definiert ist (ptrdiff_t)0
oder dass streng konformer Code erforderlich ist, um die Zeiger auf NULL zu testen? Beachten Sie, dass der einzige Fall, an dem ich interessiert bin, der Fall ist, wenn beide Zeiger NULL sind (was einen nicht initialisierten Puffer darstellt). Der Grund hat mit einer vollständig kompatiblen "verfügbaren" Funktion zu tun, sofern die vorhergehenden Annahmen erfüllt sind:
size_t buf_avail(const struct s_buf *b)
{
return b->pwrite - b->pread;
}
near
qualifizierten Nullzeiger als ein qualifizierter Nullzeiger.far
Würde er jedoch mehrere Darstellungen fürfar
Nullzeiger verwenden? Wenn ein Nullzeigernear
in einenfar
Zeiger konvertiert wird, der wiederum mit einem Nullzeiger verglichen wird,far
wenn z. B. DS gleich 0x1234 ist, passiert Folgendes: (1) 0x0000 wird zu 0x0000: 0x0000 zusammengeführt; (2) 0x0000 wird in 0x1234: 0x0000 konvertiert, aber der Vergleichsoperator prüft, ob beide Segmente Null sind, oder (3) 0x0000 wird in 0x1234: 0x0000 konvertiert, was ungleich 0x0000: 0x0000 ist.Antworten:
In C99 ist es technisch undefiniertes Verhalten. C99 §6.5.6 sagt:
Und §6.3.2.3 / 3 sagt:
Da ein Nullzeiger für kein Objekt ungleich ist, verletzt er die Voraussetzungen von 6.5.6 / 9, sodass es sich um ein undefiniertes Verhalten handelt. In der Praxis wäre ich jedoch bereit zu wetten, dass so ziemlich jeder Compiler ein Ergebnis von 0 ohne negative Nebenwirkungen zurückgibt.
In C89 ist es auch undefiniertes Verhalten, obwohl der Wortlaut des Standards etwas anders ist.
C ++ 03 hingegen hat in dieser Instanz ein definiertes Verhalten. Der Standard macht eine besondere Ausnahme für das Subtrahieren von zwei Nullzeigern. C ++ 03 §5.7 / 7 sagt:
C ++ 11 (sowie der neueste Entwurf von C ++ 14, n3690) haben den gleichen Wortlaut wie C ++ 03, nur mit der geringfügigen Änderung von
std::ptrdiff_t
anstelle vonptrdiff_t
.quelle
Ich fand dies im C ++ - Standard (5.7 [expr.add] / 7):
Wie andere gesagt haben, erfordert C99 das Addieren / Subtrahieren zwischen 2 Zeigern desselben Array-Objekts. NULL zeigt nicht auf ein gültiges Objekt, weshalb Sie es nicht für die Subtraktion verwenden können.
quelle
Bearbeiten : Diese Antwort gilt nur für C, ich habe das C ++ - Tag nicht gesehen, als ich geantwortet habe.
Nein, Zeigerarithmetik ist nur für Zeiger zulässig, die auf dasselbe Objekt zeigen. Da per Definition des C-Standards Nullzeiger auf kein Objekt zeigen, ist dies ein undefiniertes Verhalten.
(Ich würde zwar vermuten, dass jeder vernünftige Compiler nur darauf zurückkommt
0
, aber wer weiß.)quelle
std::ptrdiff_t
. "Der C-Standard stellt in diesem Fall keine Anforderungen an das Verhalten, aber viele Implementierungen spezifizieren das Verhalten der Zeigerarithmetik in vielen Fällen über die vom Standard geforderten Mindestanforderungen hinaus, einschließlich dieses.
Bei jeder konformen C-Implementierung und bei fast allen (wenn nicht allen) Implementierungen von C-ähnlichen Dialekten gelten die folgenden Garantien für jeden Zeiger
p
, der entweder ein Objekt identifiziert*p
oder*(p-1)
identifiziert:z
, der gleich Null ist, sind die Zeigerwerte(p+z)
und(p-z)
in jeder Hinsicht gleichwertigp
, mit der Ausnahme, dass sie nur dann konstant sind, wenn beidep
undz
konstant sind.q
die äquivalent sindp
, ergeben die Ausdrückep-q
undq-p
beide Null.Wenn solche Garantien für alle Zeigerwerte, einschließlich Null, gelten, müssen möglicherweise keine Nullprüfungen im Benutzercode durchgeführt werden. Darüber hinaus wäre es auf den meisten Plattformen einfacher und billiger, Code zu generieren, der solche Garantien für alle Zeigerwerte ohne Rücksicht darauf, ob sie null sind, einhält, als speziell Nullen zu behandeln. Einige Plattformen können jedoch bei Versuchen, eine Zeigerarithmetik mit Nullzeigern durchzuführen, abfangen, selbst wenn Null addiert oder subtrahiert wird. Auf solchen Plattformen würde die Anzahl der vom Compiler generierten Nullprüfungen, die zu Zeigeroperationen hinzugefügt werden müssten, um die Garantie aufrechtzuerhalten, in vielen Fällen die Anzahl der vom Benutzer generierten Nullprüfungen, die als Ergebnis weggelassen werden könnten, erheblich überschreiten.
Wenn es eine Implementierung gäbe, bei der die Kosten für die Aufrechterhaltung der Garantien hoch wären, aber nur wenige, wenn Programme davon profitieren würden, wäre es sinnvoll, zuzulassen, dass "Null + Null" -Berechnungen abgefangen werden und dieser Benutzercode für erforderlich ist Eine solche Implementierung umfasst die manuelle Nullprüfung, die die Garantien möglicherweise unnötig gemacht haben. Es wurde nicht erwartet, dass eine solche Wertberichtigung die anderen 99,44% der Implementierungen betrifft, bei denen der Wert der Aufrechterhaltung der Garantien die Kosten übersteigen würde. Solche Implementierungen sollten solche Garantien aufrechterhalten, aber ihre Autoren sollten die Autoren des Standards nicht brauchen, um ihnen dies mitzuteilen.
Die Autoren von C ++ haben entschieden, dass konforme Implementierungen die oben genannten Garantien um jeden Preis einhalten müssen, selbst auf Plattformen, auf denen sie die Leistung der Zeigerarithmetik erheblich beeinträchtigen könnten. Sie urteilten, dass der Wert der Garantien selbst auf Plattformen, deren Aufrechterhaltung teuer wäre, die Kosten übersteigen würde. Eine solche Einstellung könnte durch den Wunsch beeinflusst worden sein, C ++ als eine höhere Sprache als C zu behandeln. Von AC-Programmierern könnte erwartet werden, dass sie wissen, wann eine bestimmte Zielplattform Fälle wie (null + null) auf ungewöhnliche Weise behandeln würde, aber C ++ - Programmierer Es wurde nicht erwartet, dass sie sich mit solchen Dingen befassen. Die Gewährleistung eines konsistenten Verhaltensmodells wurde daher als die Kosten wert beurteilt.
Fragen, was "definiert" ist, haben heutzutage natürlich selten etwas damit zu tun, welche Verhaltensweisen eine Plattform unterstützen kann. Stattdessen ist es jetzt für Compiler in Mode, im Namen der "Optimierung" zu verlangen, dass Programmierer manuell Code schreiben, um Eckfälle zu behandeln, die Plattformen zuvor korrekt behandelt hätten. Wenn beispielsweise Code, der
n
Zeichen ab der Adresse ausgebenp
soll, wie folgt geschrieben wird:void out_characters(unsigned char *p, int n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); }
ältere Compiler würden Code generieren, der zuverlässig nichts ohne Nebenwirkungen ausgeben würde, wenn p == NULL und n == 0, ohne dass ein Sonderfall n == 0 erforderlich wäre. Bei neueren Compilern müsste man jedoch zusätzlichen Code hinzufügen:
void out_characters(unsigned char *p, int n) { if (n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); } }
was ein Optimierer möglicherweise loswerden kann oder nicht. Wenn der zusätzliche Code nicht eingeschlossen wird, stellen einige Compiler möglicherweise fest, dass, da p "möglicherweise nicht null sein kann", alle nachfolgenden Nullzeigerprüfungen weggelassen werden können, wodurch der Code an einer Stelle unterbrochen wird, die nicht mit dem tatsächlichen "Problem" zusammenhängt.
quelle