Pegue o endereço de um elemento de array passado-a-fim via subscrito: legal pelo padrão C ++ ou não?

Eu já vi isso várias vezes agora que o seguinte código não é permitido pelo padrão C ++:

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

O código C ++ legal do &array[5] nesse contexto?

Eu gostaria de uma resposta com uma referência ao padrão, se possível.

Também seria interessante saber se ele atende ao padrão C. E se não é o padrão C ++, por que a decisão foi tomada para tratá-lo diferentemente de array + 5 ou &array[4] + 1 ?

    Seu exemplo é legal, mas apenas porque você não está realmente usando um ponteiro fora dos limites.

    Vamos lidar com os pointers fora dos limites primeiro (porque é assim que eu originalmente interpretei a sua pergunta, antes de perceber que o exemplo usa um ponteiro passado-a-fim):

    Em geral, você não tem permissão para criar um ponteiro fora dos limites. Um ponteiro deve apontar para um elemento dentro da matriz ou para um após o final . Em nenhum outro lugar.

    O ponteiro não pode sequer existir, o que significa que você obviamente não tem permissão para cancelar a referência.

    Veja o que o padrão tem a dizer sobre o assunto:

    5,7: 5:

    Quando uma expressão que possui um tipo integral é adicionada ou subtraída de um ponteiro, o resultado tem o tipo do operando do ponteiro. Se o operando ponteiro apontar para um elemento de um object de matriz e o array for grande o suficiente, o resultado apontará para um deslocamento de elemento do elemento original, de modo que a diferença dos índices dos elementos de matriz original e resultante seja igual à expressão integral. Em outras palavras, se a expressão P apontar para o i-ésimo elemento de um object de matriz, as expressões (P) + N (equivalentemente, N + (P)) e (P) -N (onde N tem o valor n) apontam para, respectivamente, os elementos i + n-th e i-n-th do object array, desde que existam. Além disso, se a expressão P aponta para o último elemento de um object de matriz, a expressão (P) +1 aponta um passado para o último elemento do object de matriz, e se a expressão Q aponta um passado para o último elemento de um object de matriz, a expressão (Q) -1 aponta para o último elemento do object da matriz. Se tanto o operando do ponteiro quanto o resultado apontarem para elementos do mesmo object da matriz, ou um após o último elemento do object da matriz, a avaliação não produzirá um overflow; caso contrário, o comportamento é indefinido .

    (ênfase minha)

    Claro, isso é para o operador +. Então, só para ter certeza, aqui está o que o padrão diz sobre subscrição de matriz:

    5.2.1: 1:

    A expressão E1[E2] é idêntica (por definição) a *((E1)+(E2))

    Claro, há uma advertência óbvia: seu exemplo não mostra um ponteiro fora dos limites. ele usa um ponteiro “one past the end”, que é diferente. O ponteiro é permitido existir (como o acima diz), mas o padrão, tanto quanto eu posso ver, não diz nada sobre desreferenciação. O mais próximo que eu posso encontrar é 3.9.2: 3:

    [Nota: por exemplo, o endereço passado após o final de uma matriz (5.7) seria considerado como apontando para um object não relacionado do tipo de elemento da matriz que poderia estar localizado naquele endereço. – end note]

    O que me parece implicar que sim, você pode excluí-la legalmente, mas o resultado de ler ou escrever no local não é especificado.

    Graças ao ilproxyil para corrigir o último bit aqui, respondendo a última parte da sua pergunta:

    • array + 5 não desreferencia nada, simplesmente cria um ponteiro para um passado do final do array .
    • &array[4] + 1 dereferences array+4 (o que é perfeitamente seguro), pega o endereço desse lvalue e adiciona um a esse endereço, o que resulta em um ponteiro passado a fim (mas esse ponteiro nunca é desreferenciado .
    • &array[5] dereferences array + 5 (que, até onde eu vejo, é legal, e resulta em “um object não relacionado do tipo de elemento da matriz”, como dito acima), e então pega o endereço daquele elemento, que também parece legal o suficiente.

    Então eles não fazem exatamente a mesma coisa, embora neste caso, o resultado final seja o mesmo.

    Sim, é legal. Do padrão de esboço C99 :

    §6.5.2.1, parágrafo 2:

    Uma expressão postfix seguida por uma expressão entre colchetes [] é uma designação subscrita de um elemento de um object de matriz. A definição do operador de subscrição [] é que E1[E2] é idêntico a (*((E1)+(E2))) . Por causa das regras de conversão que se aplicam ao operador binário, se E1 é um object de matriz (equivalentemente, um ponteiro para o elemento inicial de um object de matriz) e E2 é um inteiro, E1[E2] designa o elemento E2 -th E1 (contando de zero).

    §6.5.3.2, parágrafo 3 (grifo meu):

    O operador unário & gera o endereço do operando. Se o operando tiver o tipo ” type ”, o resultado terá o tipo ” pointer to type ”. Se o operando é o resultado de um operador unário * , nem o operador nem o operador & são avaliados e o resultado é como se ambos fossem omitidos, exceto que as restrições nos operadores ainda se aplicam e o resultado não é um lvalue. Da mesma forma, se o operando é o resultado de um operador [] , nem o operador & nem o unário * implícito por [] são avaliados e o resultado é como se o operador & fosse removido e o operador [] fosse alterado para um operador + . Caso contrário, o resultado é um ponteiro para o object ou function designada por seu operando.

    §6.5.6, parágrafo 8:

    Quando uma expressão que tenha um tipo inteiro é adicionada ou subtraída de um ponteiro, o resultado tem o tipo do operando do ponteiro. Se o operando ponteiro apontar para um elemento de um object de matriz e o array for grande o suficiente, o resultado apontará para um deslocamento de elemento do elemento original, de forma que a diferença dos índices dos elementos de matriz resultante e original seja igual à expressão de inteiro. Em outras palavras, se a expressão P aponta para o i -ésimo elemento de um object de matriz, as expressões (P)+N (equivalentemente, N+(P) ) e (P)-N (onde N tem o valor n ) apontam para, respectivamente, os elementos i+n -th e i−n -th do object array, desde que existam. Além disso, se a expressão P aponta para o último elemento de um object de matriz, a expressão (P)+1 aponta um passado para o último elemento do object de matriz, e se a expressão Q aponta um passado para o último elemento de um object de matriz, a expressão (Q)-1 aponta para o último elemento do object da matriz. Se tanto o operando do ponteiro quanto o resultado apontarem para elementos do mesmo object da matriz, ou um após o último elemento do object da matriz, a avaliação não produzirá um estouro; caso contrário, o comportamento é indefinido. Se o resultado apontar um passado para o último elemento do object de matriz, ele não deve ser usado como o operando de um operador unário * que é avaliado.

    Observe que o padrão permite explicitamente que os pointers apontem um elemento após o final do array, desde que não sejam desreferenciados . Por 6.5.2.1 e 6.5.3.2, a expressão &array[5] é equivalente a &*(array + 5) , que é equivalente a (array+5) , que aponta um após o final do array. Isso não resulta em desreferenciamento (por 6.5.3.2), por isso é legal.

    É legal.

    De acordo com a documentação do gcc para C ++ , o &array[5] é legal. Em C ++ e em C, você pode endereçar o elemento com segurança após o final de uma matriz – você obterá um ponteiro válido. So &array[5] como uma expressão é legal.

    No entanto, ainda é um comportamento indefinido tentar cancelar a referência de pointers à memory não alocada, mesmo se o ponteiro apontar para um endereço válido. Portanto, tentar excluir a referência do ponteiro gerado por essa expressão ainda é um comportamento indefinido (isto é, ilegal), mesmo que o próprio ponteiro seja válido.

    Na prática, imagino que normalmente não causaria uma falha.

    Edit: By the way, este é geralmente como o iterator end () para contêineres STL é implementado (como um ponteiro para um-passado-o-final), de modo que é um bom testamento para a prática de ser legal.

    Edit: Oh, agora eu vejo que você não está realmente perguntando se segurando um ponteiro para esse endereço é legal, mas se essa maneira exata de obter o ponteiro é legal. Eu vou discutir com os outros respondentes sobre isso.

    Eu acredito que isso é legal, e isso depende da conversão do lvalue para rvalue. A última linha Núcleo 232 tem o seguinte:

    Concordamos que a abordagem no padrão parece bem: p = 0; * p; não é inerentemente um erro. Uma conversão lvalue para rvalue daria um comportamento indefinido

    Embora este seja um exemplo ligeiramente diferente, o que ele mostra é que o ‘*’ não resulta em lvalue para rvalue conversion e assim, dado que a expressão é o operando imediato de ‘&’ que espera um lvalue então o comportamento é definido.

    Não acredito que seja ilegal, mas acredito que o comportamento de & array [5] seja indefinido.

    • 5.2.1 [expr.sub] E1 [E2] é idêntico (por definição) a * ((E1) + (E2))

    • 5.3.1 [expr.unary.op] unary * operator … o resultado é um lvalue referente ao object ou function para a qual a expressão aponta.

    Neste ponto, você tem um comportamento indefinido porque a expressão ((E1) + (E2)) não apontou para um object e o padrão diz qual deve ser o resultado, a menos que o faça.

    • 1.3.12 [defns.undefined] O comportamento indefinido também pode ser esperado quando esta Norma omite a descrição de qualquer definição explícita de comportamento.

    Como observado em outro lugar, array + 5 e &array[0] + 5 são formas válidas e bem definidas de obter um ponteiro além do final do array.

    Além das respostas acima, vou apontar o operador e pode ser substituído por classs. Então, mesmo que fosse válido para PODs, provavelmente não é uma boa idéia fazer para um object que você sabe que não é válido (muito como replace o operador & () em primeiro lugar).

    Isso é legal:

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

    Seção 5.2.1 Subscrição A expressão E1 [E2] é idêntica (por definição) a * ((E1) + (E2))

    Então, por isso, podemos dizer que array_end também é equivalente:

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

    Seção 5.3.1.1 Operador unário ‘*’: O operador unário * executa a indireção: a expressão à qual ele é aplicado deve ser um ponteiro para um tipo de object ou um ponteiro para um tipo de function e o resultado é um lverlme referente ao object ou function para a qual a expressão aponta. Se o tipo da expressão for “pointer to T”, o tipo do resultado será “T.” [Nota: um ponteiro para um tipo incompleto (diferente de cv void) pode ser desreferenciado. O lvalue assim obtido pode ser usado de maneira limitada (para inicializar uma referência, por exemplo); este lvalue não deve ser convertido para um rvalue, ver 4.1. – nota final

    A parte importante do acima:

    ‘o resultado é um lvalue referente ao object ou function’.

    O operador unário ‘*’ está retornando um lvalue referente ao int (sem de-referência). O operador unário ‘&’ então obtém o endereço do lvalue.

    Enquanto não houver retirada de referência de um ponteiro fora dos limites, a operação é totalmente coberta pelo padrão e todo o comportamento é definido. Então, pela minha leitura, o que precede é completamente legal.

    O fato de muitos dos algoritmos STL dependerem do comportamento ser bem definido é uma espécie de indício de que o comitê de padrões já passou por isso e tenho certeza de que há algo que cobre isso explicitamente.

    A seção de comentários abaixo apresenta dois argumentos:

    (Por favor, leia: mas é longo e nós dois acabamos trollish)

    Argumento 1

    isso é ilegal por causa da seção 5.7, parágrafo 5

    Quando uma expressão que possui um tipo integral é adicionada ou subtraída de um ponteiro, o resultado tem o tipo do operando do ponteiro. Se o operando ponteiro apontar para um elemento de um object de matriz e o array for grande o suficiente, o resultado apontará para um deslocamento de elemento do elemento original, de modo que a diferença dos índices dos elementos de matriz original e resultante seja igual à expressão integral. Em outras palavras, se a expressão P apontar para o i-ésimo elemento de um object de matriz, as expressões (P) + N (equivalentemente, N + (P)) e (P) -N (onde N tem o valor n) apontam para, respectivamente, os elementos i + n-th e i-n-th do object array, desde que existam. Além disso, se a expressão P aponta para o último elemento de um object de matriz, a expressão (P) +1 aponta um passado para o último elemento do object de matriz, e se a expressão Q aponta um passado para o último elemento de um object de matriz, a expressão (Q) -1 aponta para o último elemento do object da matriz. Se tanto o operando do ponteiro quanto o resultado apontarem para elementos do mesmo object da matriz, ou um após o último elemento do object da matriz, a avaliação não produzirá um estouro; caso contrário, o comportamento é indefinido.

    E embora a seção seja relevante; não mostra comportamento indefinido. Todos os elementos da matriz sobre a qual estamos falando estão dentro da matriz ou após o final (o que está bem definido no parágrafo acima).

    Argumento 2:

    O segundo argumento apresentado abaixo é: * é o operador de referência.
    E embora este seja um termo comum usado para descrever o operador ‘*’; este termo é deliberadamente evitado no padrão, pois o termo ‘de-reference’ não é bem definido em termos da linguagem e o que isso significa para o hardware subjacente.

    Embora o access à memory além do final da matriz seja definitivamente um comportamento indefinido. Não estou convencido de que o unary * operator acesse a memory (lê / grava na memory) nesse contexto (não de maneira que o padrão defina). Neste contexto (conforme definido pelo padrão (consulte 5.3.1.1)), o unary * operator retorna um lvalue referring to the object . No meu entendimento da linguagem, isso não é access à memory subjacente. O resultado dessa expressão é imediatamente usado pelo unary & operator que retorna o endereço do object referido pelo lvalue referring to the object .

    Muitas outras referências à Wikipedia e fonts não canônicas são apresentadas. Tudo o que acho irrelevante. C ++ é definido pelo padrão .

    Conclusão:

    Estou disposto a admitir que há muitas partes do padrão que talvez não tenha considerado e que possam provar que meus argumentos acima estão errados. NÃO são fornecidos abaixo. Se você me mostrar uma referência padrão que mostra isso é UB. eu vou

    1. Deixe a resposta.
    2. Coloque tudo em maiúsculas isso é estúpido e eu estou errado para todos lerem.

    Isto não é um argumento:

    Nem tudo no mundo inteiro é definido pelo padrão C ++. Abre a tua mente.

    Esboço de trabalho ( n2798 ):

    “O resultado do operador unário & é um ponteiro para seu operando. O operando deve ser um lvalue ou um id quali fi cado. No primeiro caso, se o tipo da expressão for” T “, o tipo do resultado será” ponteiro para T. ”(p. 103)

    array [5] não é um id qualificado da melhor forma possível (a lista está na p. 87); o mais próximo parece ser identificador, mas enquanto matriz é uma matriz de identificadores [5] não é. Não é um lvalue porque “Um lvalor se refere a um object ou function” (p. 76). array [5] obviamente não é uma function, e não é garantido que se refira a um object válido (porque array + 5 é após o último elemento de array alocado).

    Obviamente, pode funcionar em certos casos, mas não é válido em C ++ ou seguro.

    Nota: É legal adicionar para obter um passado da matriz (p. 113):

    “se a expressão P [um ponteiro] aponta para o último elemento de um object de matriz, a expressão (P) +1 aponta um passado para o último elemento do object de matriz e se a expressão Q aponta um passado para o último elemento de um object de matriz object de matriz, a expressão (Q) -1 aponta para o último elemento do object de matriz.Se tanto o operando de ponteiro eo resultado apontam para elementos do mesmo object de matriz, ou um após o último elemento do object de matriz, a avaliação não deve produzir um overflow ”

    Mas não é legal fazê-lo usando &.

    Mesmo se for legal, por que sair da convenção? array + 5 é menor de qualquer maneira, e na minha opinião, mais legível.

    Edit: Se você quiser simétrico você pode escrever

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

    Deve ser um comportamento indefinido pelas seguintes razões:

    1. Tentar acessar elementos fora dos limites resulta em comportamento indefinido. Portanto, o padrão não proíbe uma implementação lançando uma exceção nesse caso (isto é, um limite de verificação de implementação antes de um elemento ser acessado). Se & (array[size]) fosse definido como begin (array) + size , uma implementação lançando uma exceção no caso de access fora do limite não estaria mais de acordo com o padrão.

    2. É impossível fazer com que este rendimento end (array) se array não é um array, mas sim um tipo de coleção arbitrário.

    Padrão C ++, 5.19, parágrafo 4:

    Uma expressão constante de endereço é um ponteiro para um lvalue …. O ponteiro deve ser criado explicitamente, usando o unário & operador … ou usando uma expressão de matriz (4.2) … tipo. O operador de subscrição [] … pode ser usado na criação de uma expressão constante de endereço, mas o valor de um object não deve ser acessado pelo uso desses operadores. Se o operador subscrito for usado, um de seus operandos deve ser uma expressão constante integral.

    Parece-me que & array [5] é legal C ++, sendo uma expressão constante de endereço.

    Se o seu exemplo NÃO é um caso geral, mas um caso específico, então é permitido. Você pode legalmente , AFAIK, mover um passado o bloco de memory alocado. It does not work for a generic case though ie where you are trying to access elements farther by 1 from the end of an array.

    Just searched C-Faq : link text

    É perfeitamente legal.

    The vector<> template class from the stl does exactly this when you call myVec.end(): it gets you a pointer (here as an iterator) which points one element past the end of the array.