Leve o endereço de um elemento de matriz one-past-the-end via subscrito: legal pela ++ Standard ou não C?

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

Pergunta

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

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

é o código &array[5] C ++ jurídica neste contexto?

Eu gostaria de uma resposta com uma referência à norma, se possível.

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

Foi útil?

Solução

O seu exemplo é legal, mas só porque você não está realmente usando um fora do ponteiro limites.

Vamos lidar com fora de ponteiros Bounds primeira (porque é assim que eu originalmente interpretados sua pergunta, antes de eu notei que o exemplo usa um ponteiro one-past-the-end em vez):

Em geral, você não está sequer autorizados a criar um ponteiro para fora da quadra. Um ponteiro deve apontar para um elemento no interior da matriz, ou um após o final . Em nenhum outro lugar.

O ponteiro é nem mesmo permissão para existir, o que significa que, obviamente, não tem permissão para excluir a referência que quer.

Aqui está o que o padrão tem a dizer sobre o assunto:

5,7: 5:

Quando uma expressão que tem integrante tipo é adicionado ou subtraído de um ponteiro, o resultado tem o tipo de Operando o ponteiro. Se o ponteiro operando aponta para um elemento de uma matriz de objecto, e a matriz é grande suficientes, o resultado aponta para uma elemento de deslocamento a partir do original elemento de tal modo que a diferença de os índices de e resultantes elementos da matriz é igual a originais expressão integral. Em outras palavras, Se a expressão de P aponta para o i-ésimo elemento de um objecto de matriz, o expressões (P) + N (equivalentemente, N + (P)) e (P) N (onde N tem o valor n) aponte para, respectivamente, o i + n-ésima e i-N-th elementos do objeto da matriz, desde que existam. Além disso, se os pontos P expressão para o último elemento de uma matriz objeto, a expressão (P) +1 pontos um após o último elemento da matriz objeto e se os pontos de expressão Q um após o último elemento de uma matriz objeto, a expressão (Q) -1 pontos de o último elemento do objeto de matriz. Se tanto o operando ponteiro eo resultar ponto para elementos da mesma matriz de objeto, ou um após o último elemento do objeto de matriz, o avaliação não produzirá ao longo do fluxo; caso contrário, o comportamento é unde fi nida .

(grifo meu)

Claro, isso é para o operador +. Então, só para ter certeza, aqui está o que a norma diz sobre subscripting array:

5.2.1: 1:

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

É claro, há uma ressalva óbvia: O seu exemplo não realmente mostrar um ponteiro para fora da quadra. que utiliza um "um após o final" ponteiro, o qual é diferente. O ponteiro é permitido a existir (como o acima diz), mas o padrão, tanto quanto eu posso ver, não diz nada sobre dereferencing-lo. O mais próximo que posso encontrar é 3.9.2: 3:

[Nota: por exemplo, a um endereço para além da extremidade de uma matriz (5,7) seria considerado apontar para um objecto relacionado do tipo do elemento da matriz que pode ser localizado nesse endereço. -end nota]

O que me parece implicar que sim, você pode legalmente eliminar referência, mas o resultado de ler ou escrever para o local não é especificado.

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

  • array + 5 na verdade não dereference nada, ele simplesmente cria um ponteiro para uma passado a extremidade de array.
  • dereferences &array[4] + 1 array+4 (que é perfeitamente seguro), leva o endereço dessa lvalue, e adiciona um para esse endereço, o que resulta em um ponteiro de um passado-a-extremidade (Mas esse ponteiro nunca fica referenciado.
  • dereferences &array[5] gama + 5 (Que, tanto quanto eu posso ver é legal, e resulta em "um objeto relacionado do array tipo de elemento", como o acima referido), e, em seguida, converte o endereço desse elemento, que também parece bastante legal.

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

Outras dicas

Sim, é legal. Do C99 projecto padrão:

§6.5.2.1, parágrafo 2:

A expressão sufixo seguido por uma expressão entre parêntesis rectos [] é um subscripted designação de um elemento de um objecto matriz. A definição do índice [] operador é que E1[E2] é idêntico ao (*((E1)+(E2))). Por causa das regras de conversão que aplicar ao operador + binário, se E1 é um objeto de matriz (de modo equivalente, um ponteiro para o elemento inicial de um objecto matriz) e E2 é um número inteiro, E1[E2] designa o E2-th elemento de E1 (contagem de zero).

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

O operador & unário produz o endereço do seu operando. Se o operando tem o tipo ‘‘ tipo ’’, o resultado tem tipo ‘‘ponteiro para tipo ’’. Se o operando é o resultado de um operador * unário, nem o operador nem o operador & é avaliada e o resultado é como se ambos fossem omitido, exceto que as restrições sobre os 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 que está implícito no [] é avaliada e o resultado é como se o operador & foram removidos e o operador [] foram alterados para um operador + . Caso contrário, o resultado é um apontador para o objecto ou função designada pelo seu operando.

§6.5.6, n.º 8:

Quando uma expressão que tem o tipo de número inteiro é adicionado ou subtraído de um ponteiro, o resultado tem o tipo de operando do ponteiro. Se o ponteiro operando pontos para um elemento de um objeto da matriz, e a matriz é suficientemente grande, o resultado aponta para um elemento de deslocamento o elemento original de modo a que a diferença dos índices do resultante e original elementos da matriz é igual a expressão inteiro. Em outras palavras, se o P expressão aponta para o i-th elemento de um objecto de matriz, o expressões (P)+N (equivalentemente, N+(P)) e (P)-N (onde N tem o valor n) aponte para, respectivamente, o i+n-th e i−n-th elementos de o objeto de matriz, desde que existam. Além disso, se a expressão P aponta para o último elemento de um objecto de matriz, o (P)+1 expressão aponta um após o último elemento da objeto da matriz, e se o Q expressão aponta um após o último elemento de um objecto matriz, o (Q)-1 expressão aponta para o último elemento do objeto array. Se tanto o ponteiro operando e o ponto de resultado para elementos do mesmo objecto matriz, ou uma após a última elemento do objeto de matriz, a avaliação não deve produzir uma descarga; caso contrário, o comportamento é indefinido. Se o resultado aponta um após o último elemento do objeto de matriz, é não deve ser usado como o operando de um operador de * unário que é avaliada.

Note que o padrão permite explicitamente ponteiros para ponto elemento de um após o final da matriz, , desde que eles não são referenciado . Por 6.5.2.1 e 6.5.3.2, a &array[5] expressão é equivalente a &*(array + 5), que é equivalente a (array+5), que aponta um após o final da matriz. Isso não resulta em um dereference (por 6.5.3.2), por isso é legal.

é legal.

acordo com a documentação gcc para C ++ , &array[5] é legal. Em ambos C ++ e em C pode transmitir com segurança o elemento de um para além do fim de uma matriz - você vai obter um ponteiro válido. Então &array[5] como uma expressão é legal.

comportamento No entanto, ainda não está definido para tentar ponteiros dereference a memória não alocada, mesmo que o ponteiro aponta para um endereço válido. Portanto, a tentativa de cancelar o ponteiro gerado por essa expressão ainda é um comportamento indefinido (isto é ilegal) mesmo que o ponteiro em si é válido.

Na prática, imagino que seria normalmente não causa um acidente, no entanto.

Edit: By the way, este é geralmente como o fim () iterator para contêineres STL é implementado (como um ponteiro para um-past-the-end), de modo que é um bom testemunho bonito para a prática ser legal <. / p>

Edit: Ah, agora eu vejo que você não está realmente perguntando se mantendo um ponteiro para esse endereço é legal, mas se dessa forma exata de obter o ponteiro é legal. Vou adiar para os outros respondentes sobre isso.

Eu acredito que isso é legal, e isso depende do 'lvalue para rvalue' a ter lugar conversão. A última linha questão central 232 tem o seguinte:

Nós concordamos que a abordagem no padrão parece bem: p = 0; * P; não é inerentemente um erro. Uma conversão lvalue-to-rvalue daria indefinido comportamento

Embora este seja exemplo ligeiramente diferente, o que faz show é que o '*' não resulta em lvalue à conversão rvalue e assim, dado que a expressão é o operando imediato de '&' que espera um lvalue então o comportamento é definido.

Eu não acredito que é ilegal, mas eu acredito que o comportamento do & array [5] é indefinido.

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

  • 5.3.1 [expr.unary.op] operador unário * ... o resultado é um lvalue referindo-se ao objeto ou função para a qual os pontos de expressão.

Neste ponto, você tem um comportamento indefinido porque a expressão ((E1) + (E2)) não chegou a apontar para um objeto e o padrão diz que o resultado deve ser a menos que ele faz.

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

Tal como já foi salientado, array + 5 e &array[0] + 5 são formas válidas e bem definidos de obtenção de um ponteiro de um para além do fim da matriz.

Além das respostas acima, eu vou apontar operador e pode ser substituído por classes. Assim, mesmo se era válido para PODs, ele provavelmente não é uma boa idéia para fazer por um objeto que você sabe que não é válido (muito parecido substituindo operador & () em primeiro lugar).

Este é legal:

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

Seção 5.2.1 subscripting A expressão E1 [E2] é idêntico (por definição) para * ((E1) + (E2))

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

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

Secção 5.3.1.1 operador Unário '*': O operador executa unária * indirecta: a expressão para o qual ele é aplicado deve ser um ponteiro para um tipo de objecto, ou um ponteiro para um tipo de função e o resultado é uma Ivalue referindo-se ao objecto ou função para o qual os pontos de expressão. Se o tipo de expressão é “ponteiro para T”, o tipo de resultado é “T.” [Nota: um ponteiro para um tipo incompleto (outra que não nulos CV) pode ser desreferenciado. O Ivalue assim obtido pode ser utilizado de forma limitada (para inicializar uma referência, para 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 referindo-se ao objeto ou função'.

O operador unário '*' está retornando um lvalue referindo-se à int (sem de-refeference). O operador unário '&' em seguida, recebe o endereço do lvalue.

Enquanto não existe de-referenciar de um ponteiro para fora dos limites, então a operação é totalmente coberta pelo padrão e todos comportamento é definido. Então, por minha leitura do acima é completamente legal.

O fato de que muitos dos algoritmos STL depender do comportamento a ser bem definido, é uma espécie de dica de que o comitê de padrões já que isso e eu tenho certeza que há uma coisa que cobre isso explicitamente.

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

(leia: mas é longo e nós dois acabam troll)

Argumento 1

isso é ilegal por causa da secção 5.7 n.º 5

Quando uma expressão que tem tipo integral é adicionada a ou subtraída de um ponteiro, o resultado tem o tipo de operando do ponteiro. Se o ponteiro operando pontos a um elemento de um objeto de matriz, e a matriz é suficientemente grande, o resultado aponta para um elemento de deslocamento do elemento original de tal modo que a diferença entre os índices dos elementos da matriz resultantes e originais é igual a expressão integral. Em outras palavras, se a expressão de P aponta para o i-ésimo elemento de um objecto matriz, as expressões (P) + N (de modo equivalente, N + (P)) e (P) N (onde N tem o valor de n) ponto para, respectivamente, a i + n-ésima e i - N-th elementos do objeto de matriz, desde que existam. Além disso, se os pontos P expressão para o último elemento de um objeto de matriz, a expressão (P) +1 pontos um a seguir ao último elemento do objeto da matriz, e se os pontos de expressão Q um após o último elemento de um objecto matriz, a expressão (Q) -1 aponta para o último elemento do objeto de matriz. Se tanto o ponteiro e operando o ponto resultado para elementos do mesmo objecto matriz, ou um passado o último elemento do objeto de matriz, a avaliação não deve produzir um estouro; caso contrário, o comportamento é indefinido.

E, embora a seção é relevante; ele não mostra um comportamento indefinido. Todos os elementos da matriz que estamos a falar são quer dentro da matriz ou um após o final (que está bem definido no parágrafo anterior).

Argumento 2:

O segundo argumento apresentado a seguir é: * é o operador de referência
. E, embora este é um termo comum usado para descrever o operador '*'; este termo é deliberadamente evitado na norma como o termo 'de referência' não está bem definido em termos de linguagem e que isso significa para o hardware subjacente.

Embora o acesso a memória além do fim da matriz é definitivamente um comportamento indefinido. Não estou convencido de que o unary * operator acessa a memória (lê / escreve para a memória) neste contexto (não de uma forma os define padrão). Neste contexto (como definido pelo padrão (ver 5.3.1.1)) a unary * operator retorna um lvalue referring to the object. Na minha compreensão da língua este não é o acesso à memória subjacente. O resultado desta expressão é então imediatamente utilizado pelo operador unary & operator que retorna o endereço do objeto referido pelo lvalue referring to the object.

Muitas outras referências a Wikipedia e fontes não canônicas são apresentados. Tudo o que eu acho irrelevante. C ++ é definido pela norma .

Conclusão:

Estou wiling a admitir há muitas partes do padrão que eu possa não ter considerado e pode provar meus argumentos acima errado. NÃO são fornecidos abaixo. Se você me mostrar uma referência padrão que mostra este é UB. Eu vou

  1. Deixe a resposta.
  2. Coloque em todos os tampões isto é estúpido e eu estou errado para que todos possam ler.

Este não é um argumento:

Nem tudo no mundo inteiro é definido pelo C ++ padrão. Abra sua mente.

projecto de Trabalho ( n2798 ):

"O resultado do operador unário & é um ponteiro para seu operando. o operando deve ser um lvalue ou uma quali fi ed-id. No caso primeira, se o tipo do expressão é “T”, o tipo de resultado é “ponteiro para T.”"(p. 103)

array [5] não é um-id qualificado como melhor eu posso dizer (a lista está na p 87.); o mais próximo parece ser identificador, mas enquanto matriz é uma matriz identificador [5] não é. Não é um Ivalue porque "Um Ivalue refere-se a um objecto ou função." (P. 76). array [5] não é, obviamente, uma função, e não é garantido para se referir a um objeto válido (porque gama + 5 é após o último elemento matriz alocada).

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

Nota: É legal para adicionar para obter um após o array (p 113.):

"se a express de P [um ponteiro] aponta para o último elemento de uma matriz objeto, a expressão (P) +1 pontos um após o último elemento da matriz objeto e se os pontos de expressão Q um após o último elemento de uma matriz objeto, a expressão (Q) -1 pontos de o último elemento do objeto de matriz. Se tanto o operando ponteiro eo resultar ponto para elementos da mesma matriz de objeto, ou um após o último elemento do objeto de matriz, o avaliação não produzirá sobre fl ow "

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

Mesmo que seja legal, porque partem da convenção? gama + 5 é mais curto de qualquer maneira, e na minha opinião, mais legível.

Edit: Se você quer que ele por simétrica você pode escrever

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

Deve ser um comportamento indefinido, pelas seguintes razões:

  1. Tentando acessar out-of-limites elementos resulta em um comportamento indefinido. Por isso, o padrão não proíbe uma aplicação jogando uma excepção em que caso (isto é, uma aplicação verificando limites antes de um elemento é acessado). Se & (array[size]) foram definidos para ser begin (array) + size, uma implementação lançar uma exceção em caso de out-of-bound de acesso não estaria de acordo com o padrão mais.

  2. É impossível fazer isso end (array) rendimento se a matriz não é uma matriz, mas sim um tipo de coleção arbitrária.

padrão C ++, 5,19, nº 4:

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

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

Se o seu exemplo não é um caso geral, mas um específico, então é permitido. Você pode legalmente , AFAIK, mover um passado o bloco de memória alocado. Ele não funciona para um caso genérico, embora isto é, onde você está tentando elementos de acesso mais distante por 1 a partir do final de uma matriz.

Apenas procurou C-Faq: link de texto

É perfeitamente legal.

O vector <> modelo de classe do STL faz exatamente isso quando você chamar myVec.end ():. Você fica um ponteiro (aqui como um iterador) que aponta um elemento após o final do array

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top