Ponteiro para um antes do primeiro elemento da matriz

8

Diz-se em C que quando os ponteiros se referem à mesma matriz ou a um elemento após o final dessa matriz, a aritmética e as comparações são bem definidas. Então, que tal um antes do primeiro elemento da matriz? Tudo bem desde que eu não desreferencie isso?

Dado

int a[10], *p;
p = a;

(1) É legal escrever --p?

(2) É legal escrever p-1em uma expressão?

(3) Se (2) estiver correto, posso afirmar isso p-1 < a?

Existe alguma preocupação prática para isso. Considere uma reverse()função que reverte uma string C que termina com '\0'.

#include <stdio.h>

void reverse(char *p)
{
    char *b, t;

    b = p;
    while (*p != '\0')
        p++;
    if (p == b)      /* Do I really need */
        return;      /* these two lines? */
    for (p--; b < p; b++, p--)
        t = *b, *b = *p, *p = t;
}

int main(void)
{
    char a[] = "Hello";

    reverse(a);
    printf("%s\n", a);
    return 0;
}

Eu realmente preciso fazer a verificação no código?

Por favor, compartilhe suas idéias do ponto de vista jurídico / jurídico, e como você lidaria com essas situações.

aafulei
fonte
2
(1) Pode ser legal escrevê-lo, mas o resultado é um comportamento indefinido se você o executar. Com arquiteturas segmentadas (Intel 80286, 80386, etc), o resultado pode ser completamente confuso. (2) O mesmo. (3) N / A, mas a resposta é não. Para o seu hardware e o / s, você pode estar seguro, mas o padrão C não garante isso.
Jonathan Leffler
1
Isso responde sua pergunta? Quais são os comportamentos indefinidos comuns que um programador de C ++ deve conhecer? Procure especificamente na seção ponteiros.
Isaiah

Respostas:

6

(1) É legal escrever --p?

É "legal", como a sintaxe C permite, mas invoca um comportamento indefinido. Com o objetivo de encontrar a seção relevante na norma, --pé equivalente a p = p - 1(exceto que pé avaliada apenas uma vez). Então:

C17 6.5.6 / 8

Se o operando do ponteiro e o resultado apontarem para elementos do mesmo objeto de matriz, ou um após o último elemento do objeto de matriz, a avaliação não produzirá um estouro; caso contrário, o comportamento é indefinido.

A avaliação invoca um comportamento indefinido, o que significa que não importa se você desassocia o ponteiro ou não - você já invocou um comportamento indefinido.

Além disso:

C17 6.5.6 / 9:

Quando dois ponteiros são subtraídos, ambos apontam para elementos do mesmo objeto de matriz ou um após o último elemento do objeto de matriz;

Se o seu código violar um "deve" no padrão ISO, ele invocará um comportamento indefinido.

(2) É legal escrever p-1 em uma expressão?

O mesmo que (1), comportamento indefinido.


Como exemplos de como isso pode causar problemas na prática: imagine que a matriz seja colocada no início de uma página de memória válida. Quando você diminui fora dessa página, pode haver uma exceção de hardware ou uma representação de interceptação de ponteiro. Este não é um cenário completamente improvável para microcontroladores, principalmente quando eles estão usando mapas de memória segmentada.

Lundin
fonte
Você poderia fornecer alguns comentários para o código de exemplo na pergunta? A verificação é necessária / adequada / suficiente?
aafulei 11/02
É possível que *p == '\0'no começo. Essa verificação pretende impedir p--o loop for.
aafulei 11/02
@aafulei Sim, eu percebi. Você precisa de uma verificação extra por causa do loop gravado incorretamente. A correção correta é reescrever o loop, por exemplo: godbolt.org/z/R4TuwT
Lundin
Obrigado pelo seu código. É inteligente. Eu sugiro que você o coloque na sua resposta para que mais pessoas o vejam. A única coisa é que, quando houver um número ímpar de caracteres na sequência (sem contar '\0'), haverá uma troca automática (troca consigo mesma) no final. Mas tudo bem. Além disso, aguarde um pouco mais para validação cruzada antes que eu possa fazer a marcação.
aafulei 11/02
Se p-1em uma expressão for inválido, p=p-1seria inválido. E p--é p=p-1. Você argumentaria que decrementar um ponteiro é inválido?
harper 11/02
-2

O uso desse tipo de aritmética de ponteiro é uma prática ruim de codificação, pois pode levar a um monte significativo de problemas difíceis de depurar.

Eu só precisava usar esse tipo de coisa uma vez em mais de 20 anos. Eu estava escrevendo uma função de retorno de chamada, mas não tinha acesso aos dados adequados. A função de chamada fornecia um ponteiro dentro de uma matriz adequada, e eu precisava do byte antes desse ponteiro.

Considerando que eu tinha acesso a todo o código-fonte e verifiquei o comportamento várias vezes para provar que consegui o que precisava e o revi por outros colegas, decidi que não há problema em deixá-lo ir para produção.

A solução adequada seria alterar a função de chamada para retornar o ponteiro adequado, mas isso não era viável, considerando tempo e dinheiro (a parte do software foi licenciada por terceiros).

Portanto, a[-1]é possível, mas deve ser usado SOMENTE com muito cuidado em situações muito particulares. Caso contrário, não há nenhuma boa razão para fazer esse tipo de vodu auto-prejudicial.


Nota: em uma análise adequada, no meu exemplo, é óbvio que eu não acessei um elemento antes do início de uma matriz adequada, mas o elemento antes de um ponteiro, que estava garantido dentro da mesma matriz.


Referindo-se ao código fornecido:

  • NÃO está OK usar p [-1] com reverse(a);;
  • está OK (-ish) usá-lo reverse(a+1);, porque você permanece dentro da matriz.
virolino
fonte