Leve o endereço de um elemento de matriz one-past-the-end via subscrito: legal pela ++ Standard ou não C?
-
13-09-2019 - |
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
?
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 dearray
. - 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 é queE1[E2]
é idêntico ao(*((E1)+(E2)))
. Por causa das regras de conversão que aplicar ao operador+
binário, seE1
é um objeto de matriz (de modo equivalente, um ponteiro para o elemento inicial de um objecto matriz) eE2
é um número inteiro,E1[E2]
designa oE2
-th elemento deE1
(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 oi
-th elemento de um objecto de matriz, o expressões(P)+N
(equivalentemente,N+(P)
) e(P)-N
(ondeN
tem o valorn
) aponte para, respectivamente, oi+n
-th ei−n
-th elementos de o objeto de matriz, desde que existam. Além disso, se a expressãoP
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 oQ
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.
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
- Deixe a resposta.
- 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:
-
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 serbegin (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. -
É 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