Atualmente, estou lendo um livro intitulado "Receitas Numéricas em C". Neste livro, o autor detalha como certos algoritmos funcionam inerentemente melhor se tivéssemos índices começando com 1 (não sigo inteiramente o argumento dele e esse não é o objetivo deste post), mas C sempre indexa suas matrizes começando com 0 Para contornar isso, ele sugere simplesmente diminuir o ponteiro após a alocação, por exemplo:
float *a = malloc(size);
a--;
Isso, ele diz, efetivamente fornecerá um ponteiro com um índice começando com 1, que será liberado com:
free(a + 1);
Até onde eu sei, esse comportamento é indefinido pelo padrão C. Aparentemente, este é um livro altamente respeitável dentro da comunidade HPC, então não quero simplesmente desconsiderar o que ele está dizendo, mas simplesmente decrementar um ponteiro fora do intervalo alocado parece altamente superficial para mim. Esse comportamento é "permitido" em C? Testei-o usando o gcc e o icc, e ambos os resultados parecem indicar que não estou me preocupando com nada, mas quero ser absolutamente positivo.
Respostas:
Você está certo nesse código, como
gera comportamento indefinido, de acordo com o padrão ANSI C, seção 3.3.6:
Para códigos como esse, a qualidade do código C no livro (quando eu o usei no final dos anos 90) não era considerada muito alta.
O problema com o comportamento indefinido é que, independentemente do resultado que o compilador produz, esse resultado é por definição correto (mesmo que seja altamente destrutivo e imprevisível).
Felizmente, pouquíssimos compiladores se esforçam para realmente causar um comportamento inesperado nesses casos, e a
malloc
implementação típica em máquinas usadas para HPC possui alguns dados de contabilidade imediatamente antes do endereço retornado; portanto, o decréscimo normalmente indicava esses dados de contabilidade. Não é uma boa idéia escrever lá, mas apenas criar o ponteiro é inofensivo para esses sistemas.Lembre-se de que o código pode quebrar quando o ambiente de tempo de execução for alterado ou quando o código for portado para um ambiente diferente.
fonte
Oficialmente, é um comportamento indefinido ter um ponto de ponteiro fora da matriz (exceto um após o final), mesmo que nunca seja desreferenciado .
Na prática, se o seu processador tiver um modelo de memória simples (ao contrário de estranhos como x86-16 ), e se o compilador não fornecer um erro de tempo de execução ou otimização incorreta se você criar um ponteiro inválido, o código funcionará bem.
fonte
Primeiro, é um comportamento indefinido. Atualmente, alguns compiladores de otimização ficam muito agressivos com comportamentos indefinidos. Por exemplo, como a-- nesse caso é um comportamento indefinido, o compilador pode decidir salvar uma instrução e um ciclo do processador e não diminuir a. O que é oficialmente correto e legal.
Ignorando isso, você pode subtrair 1, 2 ou 1980. Por exemplo, se eu tiver dados financeiros para os anos de 1980 a 2013, posso subtrair 1980. Agora, se usarmos float * a = malloc (tamanho); certamente há uma constante k grande tal que a - k é um ponteiro nulo. Nesse caso, realmente esperamos que algo dê errado.
Agora dê uma grande estrutura, digamos, um megabyte de tamanho. Aloque um ponteiro p apontando para duas estruturas. p-1 pode ser um ponteiro nulo. p-1 pode ser contornado (se uma estrutura for um megabyte e o bloco malloc for 900 KB desde o início do espaço de endereço). Portanto, poderia ser sem qualquer malícia do compilador que p - 1> p. As coisas podem ficar interessantes.
fonte
Permitido? Sim. Boa ideia? Normalmente não.
C é uma abreviação de linguagem assembly, e na linguagem assembly não há ponteiros, apenas endereços de memória. Os ponteiros de C são endereços de memória que têm um comportamento lateral de aumentar ou diminuir pelo tamanho do que apontam quando submetidos à aritmética. Isso simplifica o seguinte da perspectiva da sintaxe:
Matrizes não são realmente uma coisa em C; são apenas indicadores de intervalos contíguos de memória que se comportam como matrizes. O
[]
operador é uma abreviação para fazer aritmética e desreferenciamento de ponteiros, o quea[x]
realmente significa*(a + x)
.Existem razões válidas para fazer o acima, como alguns dispositivos de E / S tendo alguns
double
s mapeados em0xdeadbee7
e0xdeadbeef
. Muito poucos programas precisariam fazer isso.Ao criar o endereço de algo, como usando o
&
operador ou a chamadamalloc()
, você deseja manter intacto o ponteiro original para saber que o que ele aponta é realmente válido. Decrementar o ponteiro significa que um pouco de código incorreto pode tentar desreferenciá-lo, obtendo resultados errôneos, derrubando algo ou, dependendo do seu ambiente, cometendo uma violação de segmentação. Isso é especialmente verdadeiro com omalloc()
fato de você colocar o fardo em quem está ligandofree()
para lembrar de passar o valor original e não uma versão alterada que fará com que todos os pedaços se soltem.Se você precisar de matrizes baseadas em 1 em C, poderá fazê-lo com segurança às custas de alocar um elemento adicional que nunca será usado:
Observe que isso não faz nada para proteger contra exceder o limite superior, mas isso é fácil de lidar.
Termo aditivo:
Alguns capítulos e versos do rascunho da C99 (desculpe, é só isso que posso vincular):
§6.5.2.1.1 diz que a segunda expressão ("outro") usada com o operador subscrito é do tipo inteiro.
-1
é um número inteiro e isso tornap[-1]
válido e, portanto, também torna o ponteiro&(p[-1])
válido. Isso não implica que o acesso à memória nesse local produza um comportamento definido, mas o ponteiro ainda é um ponteiro válido.§6.5.2.2 diz que o operador de subscrito da matriz é avaliado como o equivalente a adicionar o número do elemento ao ponteiro, portanto
p[-1]
é equivalente a*(p + (-1))
. Ainda válido, mas pode não produzir um comportamento desejável.§6.5.6.8 diz (ênfase minha):
Isso significa que os resultados da aritmética do ponteiro precisam apontar para um elemento em uma matriz. Não diz que a aritmética deve ser feita de uma só vez. Portanto:
Eu recomendo fazer as coisas dessa maneira? Não, e minha resposta explica o porquê.
fonte