Nel senso più stretto, questo non è un comportamento indefinito, ma definito dall'implementazione. Quindi, anche se sconsigliabile se si prevede di supportare architetture non tradizionali, è possibile farlo.
La citazione standard data da interjay è buona, indicando UB, ma è solo il secondo miglior riscontro secondo me, dal momento che si occupa dell'aritmetica puntatore-puntatore (stranamente, uno è esplicitamente UB, mentre l'altro no). C'è un paragrafo che tratta direttamente l'operazione nella domanda:
[expr.post.incr] / [expr.pre.incr]
L'operando deve essere [...] o un puntatore a un tipo di oggetto completamente definito.
Oh, aspetta un momento, un tipo di oggetto completamente definito? È tutto? Voglio dire, davvero, tipo ? Quindi non hai bisogno di un oggetto?
Ci vuole un bel po 'di lettura per trovare effettivamente un indizio che qualcosa dentro potrebbe non essere così ben definito. Perché finora, sembra che tu sia perfettamente autorizzato a farlo, senza restrizioni.
[basic.compound] 3
fa una dichiarazione su quale tipo di puntatore si può avere e, essendo nessuno degli altri tre, il risultato dell'operazione sarebbe chiaramente inferiore a 3.4: puntatore non valido .
Tuttavia non dice che non ti è permesso avere un puntatore non valido. Al contrario, elenca alcune condizioni normali molto comuni (ad es. Fine della durata della memorizzazione) in cui i puntatori diventano regolarmente non validi. Quindi sembra che ciò accada. E senza dubbio:
[basic.stc] 4
L'indirizzamento attraverso un valore di puntatore non valido e il passaggio di un valore di puntatore non valido a una funzione di deallocazione hanno un comportamento indefinito. Qualsiasi altro uso di un valore di puntatore non valido ha un comportamento definito dall'implementazione.
Stiamo facendo un "qualsiasi altro" lì, quindi non è un comportamento indefinito, ma definito dall'implementazione, quindi generalmente ammissibile (a meno che l'implementazione non dica esplicitamente qualcosa di diverso).
Sfortunatamente, non è la fine della storia. Sebbene il risultato netto non cambi più da qui in poi, diventa più confuso, più a lungo si cerca "puntatore":
[basic.compound]
Un valore valido di un tipo di puntatore oggetto rappresenta l' indirizzo di un byte in memoria o un puntatore nullo. Se un oggetto di tipo T si trova su un indirizzo [...] A si dice che punti a quell'oggetto, indipendentemente da come è stato ottenuto il valore .
[Nota: ad esempio, l'indirizzo oltre la fine di un array verrebbe considerato come riferimento a un oggetto non correlato del tipo di elemento dell'array che potrebbe trovarsi a quell'indirizzo. [...]].
Leggi come: OK, chi se ne frega! Finché un puntatore punta da qualche parte nella memoria , sto bene?
[basic.stc.dynamic.safety] Un valore di puntatore è un puntatore derivato in modo sicuro [blah blah]
Leggi come: OK, derivato in sicurezza, qualunque cosa. Non spiega di cosa si tratta, né dice che ne ho davvero bisogno. Safely-derivati-the-diamine. Apparentemente posso ancora avere puntatori non derivati in modo sicuro. Immagino che dereferenziarli non sarebbe probabilmente una buona idea, ma è perfettamente ammissibile averli. Non dice diversamente.
Un'implementazione può avere una minore sicurezza del puntatore, nel qual caso la validità di un valore di puntatore non dipende dal fatto che si tratti di un valore di puntatore derivato in modo sicuro.
Oh, quindi potrebbe non importare, proprio quello che pensavo. Ma aspetta ... "non può"? Ciò significa che potrebbe anche . Come lo so?
In alternativa, un'implementazione può avere una rigorosa sicurezza del puntatore, nel qual caso un valore del puntatore che non è un valore del puntatore derivato in modo sicuro è un valore del puntatore non valido a meno che l'oggetto completo di riferimento abbia una durata di archiviazione dinamica e sia stato precedentemente dichiarato raggiungibile
Aspetta, quindi è anche possibile che abbia bisogno di chiamare declare_reachable()
ogni puntatore? Come lo so?
Ora puoi convertirti in intptr_t
, che è ben definito, dando una rappresentazione intera di un puntatore derivato in modo sicuro. Per cui, ovviamente, essendo un numero intero, è perfettamente legittimo e ben definito incrementarlo a piacere.
E sì, puoi convertire la parte intptr_t
posteriore in un puntatore, che è anche ben definito. Solo, non essendo il valore originale, non è più garantito che tu abbia un puntatore derivato in modo sicuro (ovviamente). Tuttavia, tutto sommato, alla lettera dello standard, pur essendo definito dall'implementazione, questa è una cosa legittima al 100% da fare:
[expr.reinterpret.cast] 5
Un valore di tipo integrale o di tipo di enumerazione può essere esplicitamente convertito in un puntatore. Un puntatore convertito in un numero intero di dimensioni sufficienti [...] e di nuovo nello stesso tipo di puntatore [...] valore originale; i mapping tra puntatori e numeri interi sono altrimenti definiti dall'implementazione.
La presa
I puntatori sono solo numeri normali, solo tu li usi come puntatori. Oh, se solo fosse vero!
Sfortunatamente, esistono architetture in cui ciò non è affatto vero, e la semplice generazione di un puntatore non valido (non dereferenziarlo, semplicemente averlo in un registro puntatori) causerà una trappola.
Quindi questa è la base dell '"implementazione definita". Ciò e il fatto che l'incremento di un puntatore ogni volta che lo desideri, come ti pare, potrebbe ovviamente causare un overflow, che lo standard non vuole affrontare. La fine dello spazio degli indirizzi dell'applicazione potrebbe non coincidere con la posizione dell'overflow e non si sa nemmeno se esiste qualcosa come l'overflow per i puntatori su una particolare architettura. Tutto sommato è un disastro da incubo non in alcuna relazione dei possibili benefici.
Affrontare la condizione di un oggetto passato dall'altro lato è semplice: l'implementazione deve semplicemente assicurarsi che nessun oggetto sia mai stato allocato, quindi l'ultimo byte nello spazio degli indirizzi è occupato. Quindi è ben definito in quanto utile e banale da garantire.