AFAIK, embora não possamos criar uma matriz de memória estática de tamanho 0, mas podemos fazê-lo com outras dinâmicas:
int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined
Como eu li, p
age como um elemento de passado passado. Eu posso imprimir o endereço que p
aponta para.
if(p)
cout << p << endl;
Embora eu tenha certeza de que não podemos desreferenciar esse ponteiro (último-último-elemento) como não podemos com iteradores (último-último-elemento), mas o que não tenho certeza é se está incrementando esse ponteiro
p
? Um comportamento indefinido (UB) é semelhante aos iteradores?p++; // UB?
c++
pointers
undefined-behavior
dynamic-arrays
Itachi Uchiwa
fonte
fonte
std::vector
item com 0.begin()
já é igual a,end()
portanto, você não pode incrementar um iterador que está apontando no início.Respostas:
Ponteiros para elementos de matrizes podem apontar para um elemento válido ou um após o final. Se você incrementa um ponteiro de uma maneira que ultrapassa o final do final, o comportamento é indefinido.
Para sua matriz de tamanho 0,
p
já está apontando um além do final, portanto, incrementar isso não é permitido.Veja C ++ 17 8.7 / 4 sobre o
+
operador (++
tem as mesmas restrições):fonte
x[i]
é o mesmo dex[i + j]
quando ambosi
ej
tem o valor 0?x[i]
é o mesmo elemento quex[i+j]
sej==0
.Eu acho que você já tem a resposta; Se você olhar um pouco mais fundo: você disse que incrementar um iterador off-the-end é UB assim: Esta resposta está no que é um iterador?
O iterador é apenas um objeto que possui um ponteiro e, incrementando esse iterador, está realmente incrementando o ponteiro que possui. Assim, em muitos aspectos, um iterador é tratado em termos de um ponteiro.
Este é do C ++ primer 5 edição de Lipmann.
Então é UB, não faça isso.
fonte
No sentido estrito, esse não é um comportamento indefinido, mas definido pela implementação. Portanto, apesar de desaconselhável, se você planeja oferecer suporte a arquiteturas não convencionais, provavelmente poderá fazê-lo.
A cotação padrão dada por interjay é boa, indicando UB, mas é apenas o segundo melhor acerto na minha opinião, pois trata da aritmética ponteiro-ponteiro (engraçado, um é explicitamente um UB, enquanto o outro não). Há um parágrafo que trata diretamente da operação na pergunta:
Oh, espere um momento, um tipo de objeto completamente definido? Isso é tudo? Quero dizer, realmente, tipo ? Então você não precisa de um objeto?
É preciso um pouco de leitura para realmente encontrar uma dica de que algo lá dentro pode não ser tão bem definido. Porque até agora, parece que você está perfeitamente autorizado a fazê-lo, sem restrições.
[basic.compound] 3
faz uma declaração sobre o tipo de ponteiro que um pode ter e, sendo nenhum dos outros três, o resultado da sua operação claramente se enquadra em 3.4: ponteiro inválido .No entanto, não diz que você não tem um ponteiro inválido. Pelo contrário, lista algumas condições normais muito comuns (por exemplo, duração do fim do armazenamento) em que os ponteiros se tornam regularmente inválidos. Então isso é aparentemente uma coisa permissível de acontecer. E realmente:
Estamos fazendo um "qualquer outro" lá, então não é um comportamento indefinido, mas definido pela implementação, portanto geralmente permitido (a menos que a implementação diga explicitamente algo diferente).
Infelizmente, esse não é o fim da história. Embora o resultado líquido não mude mais a partir de agora, ele fica mais confuso, quanto mais você procurar "ponteiro":
Leia como: OK, quem se importa! Enquanto um ponteiro apontar para algum lugar na memória , eu estou bem?
Leia como: OK, derivado com segurança, qualquer que seja. Não explica o que é isso, nem diz que eu realmente preciso. Derivado com segurança. Aparentemente, ainda posso ter indicadores não-derivados com segurança. Suponho que desferenciá-los provavelmente não seria uma boa idéia, mas é perfeitamente permitido tê-los. Não diz o contrário.
Ah, então não importa, exatamente o que eu pensava. Mas espere ... "não pode"? Isso significa que também pode . Como eu sei?
Espere, então é possível que eu precise chamar
declare_reachable()
cada ponteiro? Como eu sei?Agora, você pode converter para
intptr_t
, que está bem definido, fornecendo uma representação inteira de um ponteiro derivado com segurança. Para o qual, é claro, sendo um número inteiro, é perfeitamente legítimo e bem definido para incrementá-lo como desejar.E sim, você pode converter as
intptr_t
costas em um ponteiro, que também é bem definido. Apenas, não sendo o valor original, não é mais garantido que você tenha um ponteiro derivado com segurança (obviamente). Ainda assim, de acordo com a letra do padrão, embora definido pela implementação, isso é uma coisa 100% legítima a ser feita:A pegada
Ponteiros são apenas números inteiros comuns, apenas você os usa como ponteiros. Oh, se isso fosse verdade!
Infelizmente, existem arquiteturas onde isso não é verdade, e apenas gerar um ponteiro inválido (sem desreferenciá-lo, apenas tê-lo em um registro de ponteiro) causará uma armadilha.
Então essa é a base da "implementação definida". Isso e o fato de incrementar um ponteiro sempre que você quiser, como você pode, naturalmente, causar um estouro, com o qual o padrão não quer lidar. O final do espaço de endereço do aplicativo pode não coincidir com o local do estouro, e você nem sabe se existe um excesso de ponteiros para uma arquitetura específica. Em suma, é uma bagunça de pesadelo, sem nenhuma relação com os possíveis benefícios.
Lidar com a condição de um objeto passado, por outro lado, é fácil: a implementação deve simplesmente garantir que nenhum objeto seja alocado para que o último byte no espaço de endereço seja ocupado. Portanto, isso é bem definido, pois é útil e trivial de garantir.
fonte
+
operador (a partir da qual++
flui), o que significa que apontar após "um após o fim" é indefinido.