Ungültiger Zeiger wird wieder gültig

Lesezeit: 11 Minuten

Benutzer-Avatar
Benutzer2357112

int *p;
{
    int x = 0;
    p = &x;
}
// p is no longer valid
{
    int x = 0;
    if (&x == p) {
        *p = 2;  // Is this valid?
    }
}

Der Zugriff auf einen Zeiger, nachdem das Ding, auf das er zeigt, freigegeben wurde, ist ein undefiniertes Verhalten, aber was passiert, wenn eine spätere Zuweisung im selben Bereich erfolgt und Sie den alten Zeiger explizit mit einem Zeiger auf das neue Ding vergleichen? Wäre es wichtig gewesen, wenn ich gecastet hätte &x und p zu uintptr_t bevor du sie vergleichst?

(Ich weiß, es ist nicht garantiert, dass die beiden x Variablen belegen denselben Platz. Ich habe keinen Grund dazu, aber ich kann mir beispielsweise einen Algorithmus vorstellen, bei dem Sie eine Menge von Zeigern, die möglicherweise freigegeben wurden, mit einer Menge definitiv gültiger Zeiger schneiden und dabei die ungültigen Zeiger entfernen. Wenn ein zuvor ungültiger Zeiger gleich einem bekannten guten Zeiger ist, bin ich gespannt, was passieren würde.)

  • In dem Moment, in dem Sie einen Zeiger freigeben, wird der Verweis darauf zu einem undefinierten Verhalten, unabhängig davon, wofür der Speicher, auf den er zeigt, später verwendet wird. Das ist die Semantik.

    – glänzen

    22. August 2013 um 14:52 Uhr


  • Obwohl ich mir ziemlich sicher bin, dass der Standard besagt, dass dies ein undefiniertes Verhalten ist, bin ich mir nicht ganz sicher. Gute Frage!

    – orlp

    22. August 2013 um 14:53 Uhr

  • Ich denke, es ist gültig, wenn (und nur wenn) vergleichen Sie explizit den alten Zeiger, wie Sie geschrieben haben. Aber warum sollte man dies tun, anstatt nur den Zeiger neu zuzuweisen

    – Ingo Leonhardt

    22. August 2013 um 14:54 Uhr

  • Dies ist gültig, da Sie das überprüfen p zeigt auf eine gültige Adresse; aber nichts garantiert, dass das passieren wird p zeigen auf die zweite x.

    – Arnaud Le Blanc

    22. August 2013 um 14:55 Uhr


  • Um das, was ich an dieser Frage (+1) interessant finde, deutlicher zu machen, denke ich, dass sie umformuliert werden kann als: wenn ich behaupten kann p == &xist p immer austauschbar mit &x? Eine ähnliche Aussage gilt eindeutig für ganze Zahlen. Ist es für Zeiger?

    – R.Martinho Fernandes

    22. August 2013 um 15:02 Uhr


Nach meinem Verständnis der Norm (6.2.4. (2))

Der Wert eines Zeigers wird unbestimmt, wenn das Objekt, auf das er zeigt (oder gerade vorbei) das Ende seiner Lebensdauer erreicht.

Sie haben ein undefiniertes Verhalten, wenn Sie vergleichen

if (&x == p) {

da diese die in Anhang J.2 aufgeführten Punkte erfüllt:

— Der Wert eines Zeigers auf ein Objekt, dessen Lebensdauer abgelaufen ist, wird verwendet (6.2.4).
— Der Wert eines Objekts mit automatischer Aufbewahrungsdauer wird verwendet, solange es unbestimmt ist (6.2.4, 6.7.9, 6.8).

  • Oooh gute Punkte. Scheint, dass dies ein weiterer Punkt ist, an dem C und C++ auseinander gegangen sind.

    – R.Martinho Fernandes

    22. August 2013 um 15:15 Uhr


  • Hm. Sie dürfen den Wert des Zeigers überhaupt nicht verwenden, anstatt ihn nur nicht dereferenzieren zu dürfen? Das ist restriktiver als ich erwartet hätte. Ich frage mich, wie die Situation in C++ ist; Das C++-Standardzitat aus der anderen Antwort sagt nicht, ob es UB ist, das verwendet werden soll p im vergleich.

    – Benutzer2357112

    22. August 2013 um 15:16 Uhr


  • Der Standard ist sehr restriktiv. Das gibt viel Freiheit bei der Implementierung. Ich kann jedoch nicht garantieren, dass mein Verständnis des Standards mit dem des Ausschusses übereinstimmt.

    – Daniel Fischer

    22. August 2013 um 15:21 Uhr

  • Hmm, unbestimmt ist “entweder ein unspezifizierter Wert oder eine Trap-Darstellung”. Unspezifiziert ist kein Problem, nur Trap-Darstellung ist es. Aber unsigned char kann keine Trap-Darstellung haben, also was passiert, wenn wir ersetzen &x == p mit int *q = &x; memcmp(&q, &p, sizeof(p)) == 0?

    – orlp

    22. August 2013 um 15:21 Uhr

  • Sie können die Zeigervariable verwenden (um einen neuen Wert darin zu speichern), weil p bleibt durchgehend definiert. Nach dem ersten x den Gültigkeitsbereich verlassen hat, können Sie den darin gespeicherten Wert nicht verwenden p legitim.

    – Jonathan Leffler

    22. August 2013 um 15:22 Uhr

Benutzer-Avatar
Arne Merz

Okay, das scheint als zwei- mach das drei Teil Frage von einigen Leuten.

Erstens gab es Bedenken, ob die Verwendung des Zeigers für einen Vergleich überhaupt definiert ist.

Wie in den Kommentaren darauf hingewiesen wird, ist die bloße Verwendung des Zeigers UB, da $J.2: besagt, dass die Verwendung eines Zeigers auf ein Objekt, dessen Lebensdauer abgelaufen ist, UB ist.

Wenn dieses Hindernis jedoch passiert wird (was gut im Bereich von UB liegt, ist es kann immerhin funktionieren und auf vielen Plattformen funktionieren), hier ist, was ich über die anderen Bedenken gefunden habe:

Angesichts der Hinweise tun gleich vergleichender Code ist gültig:

C-Norm, §6.5.3.2,4:

[…] Wenn dem Zeiger ein ungültiger Wert zugewiesen wurde, ist das Verhalten des unären *-Operators undefiniert.

Obwohl eine Fußnote an dieser Stelle ausdrücklich sagt. dass die Adresse eines Objekts nach Ablauf seiner Lebensdauer ein ungültiger Zeigerwert ist, gilt hier nicht, da die if stellt sicher, dass der Wert des Zeigers die Adresse von ist x und ist somit gültig.

C++-Standard, §3.9.2,3:

Wenn sich ein Objekt vom Typ T an einer Adresse A befindet, zeigt ein Zeiger vom Typ cv T*, dessen Wert die Adresse A ist, auf dieses Objekt, unabhängig davon, wie der Wert ermittelt wurde. [ Note: For instance, the address one past the end of an array (5.7) would be considered to point to an unrelated object of the array’s element type that might be located at that address.

Emphasis is mine.

  • That’s C++, isn’t it?

    – Daniel Fischer

    Aug 22, 2013 at 15:07

  • @Arne I get the point you want to convey in the last paragraph, but as written it is not entirely true. There are compile-time related forms of UB. The IMO most insidious forms of UB are actually related to the compilation/linking bits, namely ODR violations (because they often are “no diagnostic required”). I’m having trouble finding a way to rephrase your text into a pedantically correct form, though 🙁

    – R. Martinho Fernandes

    Aug 22, 2013 at 15:11


  • If you can find similar language in the C standard, I’ll accept this. In the meantime, it’s interesting to know the C++ standard has language addressing this.

    – user2357112

    Aug 22, 2013 at 15:12

  • I think that the quote you cite does not imply that the program is defined in C++. If using p in &x == p is in itself undefined behavior in C++, it does not matter that after the comparison, p points to x according to your quote. Undefined behavior has already happened.

    – Pascal Cuoq

    Aug 22, 2013 at 15:19


  • “This explicitly includes otherwise invalid pointers (the ones past the end of an array)” those are explicitly valid values of pointers. These are not valid operands of the indirection operator, but for comparisons (== and != always, < and > if the other operand points into or one past the same array). However, here an indeterminate value is used, that’s an entirely different kettle of fish.

    – Daniel Fischer

    Aug 22, 2013 at 16:26

It will probably work with most of the compilers but it still is undefined behavior. For the C language these x are two different objects, one has ended its lifetime, so you have UB.

More seriously, some compilers may decide to fool you in a different way than you expect.

The C standard says

Two pointers compare equal if and only if both are null pointers, both
are pointers to the same object (including a pointer to an object and
a subobject at its beginning) or function, both are pointers to one
past the last element of the same array object, or one is a pointer to
one past the end of one array object and the other is a pointer to the
start of a different array object that happens to immediately follow
the first array object in the address space.

Note in particular the phrase “both are pointers to the same object”. In the sense of the standard the two “x”s are not the same object. They may happen to be realized in the same memory location, but this is to the discretion of the compiler. Since they are clearly two distinct objects, declared in different scopes the comparison should in fact never be true. So an optimizer might well cut away that branch completely.

Another aspect that has not yet been discussed of all that is that the validity of this depends on the “lifetime” of the objects and not the scope. If you’d add a possible jump into that scope

{
    int x = 0;
    p = &x;
  BLURB: ;
}
...
if (...)
...
if (something) goto BLURB;

the lifetime would extend as long as the scope of the first x is reachable. Then everything is valid behavior, but still your test would always be false, and optimized out by a decent compiler.

From all that you see that you better leave it at argument for UB, and don’t play such games in real code.

  • “So an optimizer might well cut away that branch completely.” So, it’s not undefined behaviour. If the branch cannot ever be taken, a compiler that makes it so is buggy. If the branch is ever taken and that isn’t a bug, they must point to the same object and not to distinct ones.

    – R. Martinho Fernandes

    Aug 22, 2013 at 15:17


  • No, they might point to the same memory location, that doesn’t make the two x the same object. Note that comparison of pointers is about objects, not about memory addresses. And as I said, it is UB anyhow, so a compiler may do what pleases.

    – Jens Gustedt

    Aug 22, 2013 at 15:18


  • That contradicts the part you quoted. “Two pointers compare equal if and only if (…) both are pointers to the same object (…)”. Either they don’t compare equal, or they point to the same object (I hope I can discard the other possibilities listed for this argument).

    – R. Martinho Fernandes

    Aug 22, 2013 at 15:20


  • The only other possibility is that the check itself does not have defined behaviour, something which another answer mentions, but this one doesn’t. If the check’s behaviour is defined, the standard seems quite clear on what that behaviour is.

    – R. Martinho Fernandes

    Aug 22, 2013 at 15:21


  • You can’t jump into a block past variables with initializers, so jumping to BLURB is not allowed (either strictly disallowed or invokes UB because it bypasses the initializers at the start of the block).

    – Jonathan Leffler

    Aug 22, 2013 at 15:26

It would work, if by work you use a very liberal definition, roughly equivalent to that it would not crash.

However, it is a bad idea. I cannot imagine a single reason why it is easier to cross your fingers and hope that the two local variables are stored in the same memory address than it is to write p=&x again. If this is just an academic question, then yes it’s valid C – but whether the if statement is true or not is not guaranteed to be consistent across platforms or even different programs.

Edit: To be clear, the undefined behavior is whether &x == p in the second block. The value of p will not change, it’s still a pointer to that address, that address just doesn’t belong to you anymore. Now the compiler might (probably will) put the second x at that same address (assuming there isn’t any other intervening code). If that happens to be true, it’s perfectly legal to dereference p just as you would &x, as long as it’s type is a pointer to an int or something smaller. Just like it’s legal to say p = 0x00000042; if (p == &x) {*p = whatever;}.

The behaviour is undefined. However, your question reminds me of another case where a somewhat similar concept was being employed. In the case alluded, there were these threads which would get different amounts of cpu times because of their priorities. So, thread 1 would get a little more time because thread 2 was waiting for I/O or something. Once its job was done, thread 1 would write values to the memory for the thread two to consume. This is not “sharing” the memory in a controlled way. It would write to the calling stack itself. Where variables in thread 2 would be allocated memory. Now, when thread 2 eventually got round to execution,all its declared variables would never have to be assigned values because the locations they were occupying had valid values. I don’t know what they did if something went wrong in the process but this is one of the most hellish optimizations in C code I have ever witnessed.

user avatar
Pascal Cuoq

Winner #2 in this undefined behavior contest is rather similar to your code:

#include <stdio.h>
#include <stdlib.h>

int main() {
  int *p = (int*)malloc(sizeof(int));
  int *q = (int*)realloc(p, sizeof(int));
  *p = 1;
  *q = 2;
  if (p == q)
    printf("%d %d\n", *p, *q);
}

According to the post:

Using a recent version of Clang (r160635 for x86-64 on Linux):

$ clang -O realloc.c ; ./a.out

1 2

This can only be explained if the Clang developers consider that this example, and yours, exhibit undefined behavior.

Put aside the fact if it is valid (and I’m convinced now that it’s not, see Arne Mertz’s answer) I still think that it’s academic.

The algorithm you are thinking of would not produce very useful results, as you could only compare two pointers, but you have no chance to determine if these pointers point to the same kind of object or to something completely different. A pointer to a struct could now be the address of a single char for example.

  • “I think it is if and only if you explicitely compare” See C99 J.2 Undefined behavior The value of a pointer to an object whose lifetime has ended is used (6.2.4).

    – Pascal Cuoq

    Aug 22, 2013 at 20:24

  • @Pascal Cuoq I have seen the discussion continued, after I have lefte the site yesterday. I’m convinced now

    – Ingo Leonhardt

    Aug 23, 2013 at 10:05

1355630cookie-checkUngültiger Zeiger wird wieder gültig

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy