Prendere l'indirizzo di un elemento della matrice one-past-the-end tramite pedice: legale dal ++ standard C o no?

StackOverflow https://stackoverflow.com/questions/988158

Domanda

L'ho visto affermato diverse volte che il codice seguente non è consentito dalla ++ standard C:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

è il codice &array[5] C ++ legale in questo contesto?

Vorrei una risposta con un riferimento alla norma, se possibile.

Sarebbe anche interessante sapere se soddisfa lo standard C. E se non è standard C ++, perché è stata la decisione presa per trattare in modo diverso da array + 5 o &array[4] + 1?

È stato utile?

Soluzione

Il vostro esempio è legale, ma solo perché non si sta usando un fuori puntatore limiti.

Diamo trattare con fuori limite puntatori prima (perché è così che ho inizialmente interpretato la tua domanda, prima che ho notato che l'esempio utilizza un puntatore one-past-the-end invece):

In generale, il gioco è nemmeno permesso di creare un puntatore out-of-bounds. Un puntatore deve puntare a un elemento all'interno della matrice, o uno oltre l'estremità . In nessun altro luogo.

Il puntatore non è nemmeno permesso di esistere, il che significa che non sei, ovviamente, il permesso di dereferenziarlo sia.

Ecco ciò che la norma ha da dire su questo argomento:

5.7: 5:

  

Quando un'espressione che contiene integrale   tipo è aggiunto o sottratto da un   puntatore, il risultato è il tipo di   il puntatore operando. Se il puntatore   Inoltre, operando ad un elemento di un   oggetto matrice, e la matrice è grande   abbastanza, i punti di risultato ad un   compensato dall'originale elemento   Elemento tale che la differenza di   i pedici del risultante e   elementi dell'array originali uguale al   espressione integrale. In altre parole,   se i punti P espressione per l'i-esimo   elemento di un oggetto array, la   espressioni (P) + N (in modo equivalente,   N + (P)) e (P) -N (dove N è il   valore n) indicano, rispettivamente, la   i + n-esimo e i-n-esima elementi della   oggetto array, purché esse esistono.   Inoltre, se i punti espressione P   l'ultimo elemento di un array   oggetto, l'espressione (P) +1 punti   uno dopo l'ultimo elemento dell'array   oggetto, e se i punti espressione Q   uno dopo l'ultimo elemento di un array   oggetto, l'espressione (Q) -1 indica   l'ultimo elemento dell'oggetto matrice.   Se sia il puntatore operando e il   punto risultato a elementi dello stesso   oggetto array, oppure uno dopo l'ultimo   elemento dell'oggetto matrice, la   valutazione non deve produrre un   overflow; in caso contrario, il comportamento è   unde ned fi .

(sottolineatura mia)

Naturalmente, questo è per l'operatore +. Quindi, solo per essere sicuro, ecco cosa dice la norma sulla matrice subscripting:

5.2.1: 1:

  

Il E1[E2] espressione è identica (per definizione) per *((E1)+(E2))

Naturalmente, c'è un avvertimento ovvio: Il tuo esempio in realtà non mostrano un puntatore out-of-bounds. utilizza uno "dopo la fine" puntatore, che è diverso. Il puntatore è permesso di esistere (come sopra dice), ma lo standard, per quanto posso vedere, non dice nulla su dereferencing esso. Il più vicino che posso trovare è 3.9.2: 3:

  

[Nota: per esempio, l'indirizzo di uno oltre la fine di un array (5.7) sarebbe considerata   puntare a un oggetto non correlato di tipo elemento dell'array che può essere situato a quell'indirizzo. Nota -end]

Il che mi sembra implicare che sì, si può legalmente dereferenziarlo, ma il risultato di lettura o la scrittura per la posizione non è specificato.

Grazie a ilproxyil per correggere l'ultimo pezzo qui, rispondendo l'ultima parte della tua domanda:

  • array + 5 in realtà non dereferenziarlo nulla, semplicemente crea un puntatore a una oltre la fine di array.
  • dereferenziazioni &array[4] + 1 array+4 (che è perfettamente sicuro), prende l'indirizzo di tale Ivalue, e aggiunge uno a quell'indirizzo, che risultati in un puntatore di un past the end (Ma che non viene mai puntatore Dereferenced.
  • dereferenziazioni &array[5] array + 5 (Che, per quanto posso vedere è legale, e si traduce in un" oggetto estraneo di tipo elemento dell'array", come sopra detto), e poi prende la indirizzo di tale elemento, che anche sembra abbastanza legale.

Quindi, non fanno esattamente la stessa cosa, anche se in questo caso, il risultato finale è lo stesso.

Altri suggerimenti

Sì, è legale. Dal C99 progetto di norma :

§6.5.2.1, comma 2:

  

Un'espressione postfissa seguita da un'espressione tra parentesi quadre [] è un pedice   designazione di un elemento di un oggetto array. La definizione della [] operatore pedice   è che E1[E2] è identico a (*((E1)+(E2))). A causa delle regole di conversione che   applicare all'operatore + binario, se E1 è un oggetto array (equivalentemente, un puntatore al   elemento iniziale di un oggetto array) e E2 è un numero intero, E1[E2] designa il E2-esimo   elemento di E1 (contando da zero).

§6.5.3.2, paragrafo 3 (sottolineatura mia):

  

L'operatore unario & produce l'indirizzo del suo operando. Se l'operando è di tipo ‘‘ tipo ’’,   il risultato è di tipo ‘‘puntatore a tipo ’’. Se l'operando è il risultato di un operatore unario *,   né tale operatore né l'operatore & viene valutata e il risultato è come se entrambi erano   omessi, salvo che i vincoli per gli operatori applicano ancora e il risultato non è un   lvalue. Analogamente, se l'operando è il risultato di un operatore [], né l'operatore & né il * unario che è implicata dal [] viene valutata e il risultato è come se l'operatore &   sono stati rimossi e l'operatore [] vengono cambiati ad un operatore + . In caso contrario, il risultato è   un puntatore all'oggetto o funzione designato dal suo operando.

§6.5.6, comma 8:

  

Quando si aggiunge un'espressione che contiene tipo intero o sottratto da un puntatore, la   risultato ha il tipo del puntatore operando. Se i punti di puntatore operando un elemento di   un oggetto matrice, e la matrice è abbastanza grande, i punti di risultato a un elemento offset da   l'elemento originale tale che la differenza degli indici del risultante e originale   elementi dell'array è uguale alla espressione intera. In altre parole, se l'espressione punti P a   l'elemento i-esimo di un oggetto array, l'espressioni (P)+N (equivalentemente, N+(P)) e   (P)-N (dove N ha il valore n) indicano, rispettivamente, gli elementi i+n-th e i−n-esime   L'oggetto array, purché esse esistono. Inoltre, se l'espressione P punti all'ultimo   elemento di un oggetto array, il (P)+1 espressione indica uno dopo l'ultimo elemento della   oggetto matrice, e se il Q espressione indica uno dopo l'ultimo elemento di un oggetto array,   l'espressione (Q)-1 punti all'ultimo elemento dell'oggetto matrice. Se sia il puntatore   operando e il punto risultato a elementi dello stesso oggetto array, oppure uno dopo l'ultimo   elemento dell'oggetto matrice, la valutazione non deve produrre un overflow; in caso contrario, il   comportamento non è definito. Se il risultato indica uno oltre l'ultimo elemento dell'oggetto array,   non devono essere utilizzati come operando di un operatore unario * che viene valutato.

Si noti che la norma consente esplicitamente puntatori a punto un elemento oltre la fine della matrice, a condizione che essi non sono dereferenziati . Da 6.5.2.1 e 6.5.3.2, il &array[5] espressione è equivalente a &*(array + 5), che è equivalente a (array+5), che indica uno oltre la fine della matrice. Questo non si traduca in un dereference (da 6.5.3.2), quindi è legale.

legale.

Secondo la documentazione gcc per C ++ , &array[5] è legale. Sia in C ++ e in C si può affrontare in modo sicuro l'elemento di quella oltre la fine di un array - si otterrà un puntatore valido. Così &array[5] come espressione è legale.

Tuttavia, è ancora un comportamento indefinito per tentare di puntatori dereference a memoria non allocata, anche se il puntatore punta a un indirizzo valido. Quindi il tentativo di dereference il puntatore generato da questa espressione è ancora un comportamento indefinito (cioè illegale) anche se il puntatore stesso è valido.

In pratica, immagino che non sarebbe di solito causare un crash, però.

Edit: A proposito, questo è generalmente come viene implementata la fine () iteratore per i contenitori STL (come un puntatore a un past-the-end), quindi questo è un buon testamento per la pratica di essere legale <. / p>

Edit: Oh, ora vedo che non stai veramente chiedendo se in possesso di un puntatore a tale indirizzo è legale, ma se in questo modo esatto per ottenere il puntatore è legale. Mi rimetto alle altre answerers su quella.

Credo che questo è legale, e dipende dalla 'lvalue a rvalue' la conversione in atto. La questione ultima riga Nucleo 232 ha il seguente:

  

Siamo d'accordo che l'approccio della norma sembra a posto: p = 0; * P; non è di per sé un errore. Una conversione lvalue-to-rvalue avrebbe dato comportamento non definito

Anche se questo è esempio leggermente diverso, ciò che fa mostra è che il '*' non risulta in Ivalue alla conversione rvalue e così, dato che l'espressione è l'operando immediato di 'e' che prevede un Ivalue allora il comportamento è definito.

Non credo che è illegale, ma credo che il comportamento di & array [5] non è definito.

  • 5.2.1 [expr.sub] E1 [E2] è identica (per definizione) a * ((E1) + (E2))

  • 5.3.1 [expr.unary.op] unario * operatore ... il risultato è un Ivalue riferimento all'oggetto o funzione a cui i punti di espressione.

A questo punto si ha un comportamento indefinito perché l'espressione ((E1) + (E2)) non ha in realtà ad un oggetto e lo standard non dire quello che il risultato dovrebbe essere a meno che non lo fa.

  • 1.3.12 [defns.undefined] comportamento indefinito si può prevedere anche quando questa norma internazionale omette la descrizione di una definizione esplicita di comportamento.

Come notato altrove, array + 5 e &array[0] + 5 sono modi validi e ben definite per ottenere un puntatore uno oltre la fine della matrice.

Oltre alle risposte di cui sopra, mi fanno notare operatore e possono essere ignorate per le classi. Quindi, anche se era valido per i POD, probabilmente non è una buona idea di fare per un oggetto si sa non è valido (molto simile sovrascrivendo operator & () in primo luogo).

Questa è legale:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
  

Sezione 5.2.1 subscripting L'espressione E1 [E2] è identica (per definizione) a * ((E1) + (E2))

Quindi, da questo possiamo dire che è equivalente array_end troppo:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))
  

Sezione 5.3.1.1 unario operatore '*': Il unario * operatore esegue indiretto: l'espressione a cui è applicato è un puntatore a un tipo di oggetto, o   un puntatore a un tipo di funzione e il risultato è un Ivalue riferimento all'oggetto o funzione a cui punta espressione.   Se il tipo dell'espressione è “puntatore a T”, il tipo del risultato è “T” [Nota: un puntatore a un tipo incompleto (altra   di cv vuoto) può essere dereferenziati. Lvalue così ottenuto può essere utilizzato in modo limitato (per inizializzare un riferimento, per   esempio); questo lvalue non deve essere convertito in un rvalue, vedere 4.1. - nota end]

La parte importante di quanto sopra:

  

'il risultato è un Ivalue riferimento all'oggetto o funzione'.

L'operatore unario '*' sta tornando un lvalue riferendosi al int (senza de-refeference). L'operatore unario 'e' quindi ottiene l'indirizzo del lvalue.

Finché non c'è de-riferimento di un puntatore del fuori limite, allora l'operazione è completamente coperta dallo standard e tutto il comportamento è definito. Quindi dal mio letto quanto sopra è completamente legale.

Il fatto che molti degli algoritmi STL dipendono dal comportamento di essere ben definito, è una sorta di suggerimento che la commissione standard ha già anche se di questo e sono sicuro che ci sia un qualcosa che si occupa di questo in modo esplicito.

La sezione commenti qui sotto presenta due argomenti:

(si prega di leggere: ma è lungo e tutti e due finiscono troll)

argomento 1

questo è illegale a causa della sezione 5.7 paragrafo 5

  

Quando un'espressione che ha è aggiunto tipo integrale o sottratto da un puntatore, il risultato è il tipo del puntatore operando. Se i punti puntatore operando ad un elemento di un oggetto matrice, e la matrice è abbastanza grande, i punti di risultato ad un elemento di offset dall'elemento originale tale che la differenza degli indici degli elementi di matrice risultante ed originali uguale all'espressione integrale. In altre parole, se i punti espressione P per l'elemento i-esimo di un oggetto array, le espressioni (P) + N (equivalentemente N + (P),) e -N (dove N è il valore n) del punto (P) a, rispettivamente, i + n-esimo ed i - elementi n-esimo del oggetto array, purché esse esistono. Inoltre, se i punti espressione P all'ultimo elemento di un oggetto array, l'espressione (P) +1 punti uno dopo l'ultimo elemento dell'oggetto matrice, e se i punti espressione Q uno dopo l'ultimo elemento di un oggetto array, l'espressione (Q) -1 indica l'ultimo elemento dell'oggetto matrice. Se sia il puntatore operando e il punto risultato a elementi dello stesso oggetto array, o uno passato   l'ultimo elemento della matrice oggetto, la valutazione non deve produrre un overflow; in caso contrario, il comportamento è indefinito.

E se la sezione è rilevante; non mostra un comportamento indefinito. Tutti gli elementi dell'array di cui stiamo parlando sono sia all'interno della matrice o uno oltre la fine (che è ben definita dal paragrafo precedente).

Argomento 2:

Il secondo argomento presentato di seguito è: * è l'operatore di riferimento
. E se questo è un termine comunemente usato per descrivere l'operatore '*'; questo termine è volutamente evitata nello standard come il termine 'de-reference' non è ben definito in termini di lingua e che cosa significa all'hardware sottostante.

Sebbene l'accesso alla memoria uno oltre la fine della matrice è sicuramente comportamento indefinito. Non mi convince il unary * operator accede alla memoria (lettura / scrittura di memoria) in questo contesto (non in maniera standard definisce). In questo contesto (come definito dallo standard (vedi 5.3.1.1)) il unary * operator restituisce un lvalue referring to the object. Nella mia comprensione della lingua non è l'accesso alla memoria sottostante. Il risultato di questa espressione viene immediatamente utilizzato dall'operatore unary & operator che restituisce l'indirizzo dell'oggetto cui si riferisce l'lvalue referring to the object.

Molti altri riferimenti a Wikipedia e fonti non canoniche sono presentati. Tutto ciò trovo irrilevante. C ++ è definito dallo standard .

Conclusione:

Sono hanno voglia di concedere ci sono molte parti della norma che io non ho preso in considerazione e possono dimostrare le mie argomentazioni di cui sopra sbagliato. NON sono forniti di seguito. Se mi si mostra uno standard di riferimento che mostra questo è UB. Io

  1. Lascia la risposta.
  2. Mettere in tutte le protezioni questo è stupido e io sono sbagliato per tutto da leggere.

Questo non è un argomento:

  

Non tutto in tutto il mondo è definito da standard C ++. Apri la tua mente.

bozza di lavoro ( n2798 ):

  

"Il risultato della unario operatore & è   un puntatore al suo operando. l'operando   deve essere un lvalue o un quali fi cata-id.   Nel primo caso, se il tipo di   espressione è “T”, il tipo di   risultato è “puntatore a T”"(p. 103)

array [5] non è un qualificato-id come meglio posso dire (l'elenco è a pag 87.); il più vicino sembrerebbe essere identificatore, ma mentre array è una matrice identificatore [5] non è. Non è un Ivalue perché "An lvalue si riferisce ad un oggetto o una funzione." (P. 76). array [5] non è ovviamente funzione, e non è garantito per fare riferimento a un oggetto valido (perché array + 5 è dopo l'ultimo elemento array allocato).

Ovviamente, può funzionare in alcuni casi, ma non è valido C ++ o di sicurezza.

Nota: E 'legale da aggiungere per ottenere uno oltre l'array (p 113).

  

"se l'espressione P [un puntatore]   punti per l'ultimo elemento di un array   oggetto, l'espressione (P) +1 punti   uno dopo l'ultimo elemento dell'array   oggetto, e se i punti espressione Q   uno dopo l'ultimo elemento di un array   oggetto, l'espressione (Q) -1 indica   l'ultimo elemento dell'oggetto matrice.   Se sia il puntatore operando e il   punto risultato a elementi dello stesso   oggetto array, oppure uno dopo l'ultimo   elemento dell'oggetto matrice, la   valutazione non deve produrre un   overflow "

Ma non è legale farlo utilizzando &.

Anche se è legale, perché partono convention? array + 5 è più breve in ogni caso, e, a mio parere, più leggibile.

Edit: Se si vuole che da simmetrica è possibile scrivere

int* array_begin = array; 
int* array_end = array + 5;

Dovrebbe essere un comportamento indefinito, per i seguenti motivi:

  1. Cercando di accedere fuori dal campo elementi risulta in un comportamento indefinito. Quindi la norma non vieta un'implementazione un'eccezione in tal caso (ossia un'implementazione, verifica limiti prima di un elemento si accede). Se & (array[size]) sono stati definiti per essere begin (array) + size, un'implementazione un'eccezione in caso di accesso out-of-bound non sarebbe conforme allo standard più.

  2. E 'impossibile fare questo end (array) rendimento se matrice non è un array, ma piuttosto un tipo di raccolta arbitraria.

C ++ standard 5.19, paragrafo 4:

Un indirizzo espressione costante è un puntatore a un lvalue .... Il puntatore viene creata esplicitamente, usando l'operatore unario & ... o usando un'espressione di matrice (4.2) ... tipo. L'operatore di indicizzazione [] ... può essere utilizzato per la creazione di un indirizzo espressione costante, ma il valore di un oggetto non è accessibile mediante l'uso di questi operatori. Se si utilizza l'operatore di indicizzazione, uno dei suoi operandi sarà un'espressione costante intera.

mi sembra & array [5] è C ++ legale, essendo un indirizzo espressione costante.

Se il tuo esempio non è un caso generale, ma uno specifico, allora è permesso. È possibile legalmente , per quanto ne so, spostare uno oltre il blocco allocato di memoria. Non funziona per un caso generico se cioè in cui si tenta di accedere agli elementi più lontano dalla 1 alla fine di un array.

Just cercato C-Faq: link testuale

E 'perfettamente legale.

Il vettore <> classe template dal STL fa esattamente questo quando si chiama myVec.end ():. Esso si ottiene un puntatore (qui come un iteratore), che indica un elemento oltre la fine della matrice

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top