Ponteiro aritmético para ponteiro vazio em C

Quando um ponteiro para um tipo específico (digamos int , char , float , ..) é incrementado, seu valor é aumentado pelo tamanho desse tipo de dado. Se um ponteiro void que aponta para dados de tamanho x é incrementado, como ele consegue apontar x bytes à frente? Como o compilador sabe adicionar x ao valor do ponteiro?

Conclusão final: a aritmética em um void* é ilegal em C e C ++.

O GCC permite isso como uma extensão, consulte Aritmética em void – e em Ponteiros de Função (observe que esta seção é parte do capítulo “C Extensions” do manual). O Clang e o ICC provavelmente permitem a aritmética void* para fins de compatibilidade com o GCC. Outros compiladores (como o MSVC) não permitem a aritmética no void* , e o GCC o desativa se o -pedantic-errors for especificado, ou se o -Werror-pointer-arith for especificado (este sinalizador será útil se o seu código base também precisar compilar com o MSVC).

O padrão C fala

As citações são tiradas do rascunho n1256.

A descrição do padrão da operação de adição declara:

6.5.6-2: Para adição, ambos os operandos devem ter tipo aritmético, ou um operando deve ser um ponteiro para um tipo de object e o outro deve ter um tipo inteiro.

Então, a questão aqui é se void* é um ponteiro para um “tipo de object”, ou equivalentemente, se void é um “tipo de object”. A definição para “tipo de object” é:

6.2.5.1: Os tipos são particionados em tipos de objects (tipos que descrevem completamente os objects), tipos de function (tipos que descrevem funções) e tipos incompletos (tipos que descrevem objects, mas carecem de informações necessárias para determinar seus tamanhos).

E o padrão define void como:

6.2.5-19: O tipo void compreende um conjunto vazio de valores; é um tipo incompleto que não pode ser concluído.

Como void é um tipo incompleto, não é um tipo de object. Portanto, não é um operando válido para uma operação de adição.

Portanto, você não pode executar aritmética de ponteiro em um ponteiro void .

Notas

Originalmente, pensava-se que a aritmética void* era permitida, por causa dessas seções do padrão C:

6.2.5-27: Um ponteiro para void deve ter os mesmos requisitos de representação e alinhamento que um ponteiro para um tipo de caractere.

Contudo,

Os mesmos requisitos de representação e alinhamento devem implicar a intercambialidade como argumentos para funções, valores de retorno de funções e membros de sindicatos.

Então isso significa que printf("%s", x) tem o mesmo significado se x tem o tipo char* ou void* , mas isso não significa que você pode fazer aritmética em um void* .

Nota do editor: Esta resposta foi editada para refletir a conclusão final.

A aritmética do ponteiro não é permitida em pointers void* .

lançá-lo para um ponteiro de char um incremento seu ponteiro para frente x bytes à frente.

Você não pode fazer aritmética de ponteiro em tipos void * , exatamente por este motivo!

Você tem que lançá-lo para outro tipo de ponteiro antes de fazer a aritmética do ponteiro.

Ponteiros vazios podem apontar para qualquer parte da memory. Portanto, o compilador não sabe quantos bytes para incrementar / decrementar quando tentamos aritmética de ponteiro em um ponteiro vazio. Portanto, os pointers nulos devem ser primeiro convertidos para um tipo conhecido antes que possam ser envolvidos em qualquer aritmética de ponteiro.

 void *p = malloc(sizeof(char)*10); p++; //compiler does how many where to pint the pointer after this increment operation char * c = (char *)p; c++; // compiler will increment the c by 1, since size of char is 1 byte. 

O padrão C não permite aritmética de ponteiro vazio . No entanto, o GNU C é permitido considerando que o tamanho do void é 1 .

Padrão C11 §6.2.5

§ 19

O tipo de void compreende um conjunto vazio de valores; é um tipo de object incompleto que não pode ser concluído.

Seguinte programa funcionando bem no compilador GCC.

 #include int main() { int arr[2] = {1, 2}; void *ptr = &arr; ptr = ptr + sizeof(int); printf("%d\n", *(int *)ptr); return 0; } 

Pode ser que outros compiladores gerem um erro.

Compilador sabe por tipo casting. Dado um void *x :

  • x+1 adiciona um byte a x , o ponteiro vai para byte x+1
  • (int*)x+1 adiciona sizeof(int) bytes, ponteiro vai para byte x + sizeof(int)
  • (float*)x+1 addres sizeof(float) bytes, etc.

Embora o primeiro item não seja portátil e seja contra o Galateo de C / C ++, ele é, no entanto, C-language-correcto, o que significa que irá compilar algo na maioria dos compiladores, possivelmente necessitando de uma flag apropriada (como -Winter-arith)