Aritmética de ponteiro para ponteiro vazio em C

176

Quando um ponteiro para um tipo específico (por exemplo int, char, float, ..) é incrementado, o seu valor é aumentado pelo tamanho de que tipo de dados. Se um voidponteiro que aponta para dados de tamanho xé incrementado, como ele aponta os xbytes à frente? Como o compilador sabe agregar xvalor ao ponteiro?

Siva Sankaran
fonte
3
A pergunta parece que supõe que o compilador (/ tempo de execução) sabe em que tipo de objeto o ponteiro foi definido e adiciona seu tamanho ao ponteiro. Esse é um completo equívoco: ele sabe apenas o endereço.
PJTraill 5/05
2
"Se um voidponteiro que aponta para dados de tamanho xé incrementado, como ele aponta os xbytes à frente?" Não faz. Por que as pessoas que têm essas perguntas não podem testá-las antes de perguntar - você sabe, pelo menos ao mínimo, onde verificam se realmente compila, o que não acontece. -1, não posso acreditar que isso tenha +100 e -0.
Underscore_d

Respostas:

287

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

O GCC permite isso como uma extensão, consulte Aritmética sobre void- e Ponteiros de função (observe que esta seção faz parte do capítulo "Extensões C" do manual)). Clang e ICC provavelmente permitem void*aritmética para fins de compatibilidade com o GCC. Outros compiladores (como o MSVC) desabilitam a aritmética void*e o GCC o desabilita se o -pedantic-errorssinalizador for especificado ou se o -Werror-pointer-arithsinalizador for especificado (esse sinalizador é útil se sua base de código também precisar compilar com o MSVC).

O padrão C fala

As cotações são retiradas do rascunho n1256.

A descrição da norma da operação de adição indica:

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

Portanto, a questão aqui é se void*é um ponteiro para um "tipo de objeto" ou, equivalentemente, se voidé um "tipo de objeto". A definição para "tipo de objeto" é:

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

E o padrão define voidcomo:

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

Como voidé um tipo incompleto, não é um tipo de objeto. 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 voidponteiro.

Notas

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

6.2.5-27: Um ponteiro para anular 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 intercambiabilidade como argumentos para funções, retornar valores de funções e membros de sindicatos.

Portanto, isso significa que printf("%s", x)tem o mesmo significado, seja xtype char*ou void*, mas não significa que você possa fazer aritmética em a void*.

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

Sadeq
fonte
7
Do padrão C99: (6.5.6.2) Além disso, ambos os operandos devem ter tipo aritmético ou um operando deve ser um ponteiro para um tipo de objeto e o outro deve ter um tipo inteiro. (6.2.5.19) O tipo de vazio compreende um conjunto de valores vazio; é um tipo incompleto que não pode ser concluído. Eu acho que deixa claro que a void*aritmética dos ponteiros não é permitida. O GCC tem uma extensão que permite fazer isso.
Job
1
se você não achar mais sua resposta útil, basta excluí-la.
caf
1
Essa resposta foi útil, apesar de ter se mostrado errada, pois contém provas conclusivas de que ponteiros nulos não são destinados à aritmética.
Ben Flynn
1
Esta é uma boa resposta, tem a conclusão correta e as citações necessárias, mas as pessoas que chegaram a essa pergunta chegaram à conclusão errada porque não leram o final da resposta. Eu editei isso para torná-lo mais óbvio.
Dietrich Epp
1
Clang e ICC não permitem void*aritmética (pelo menos por padrão).
Sergey Podobry
61

Aritmética de ponteiro não é permitida em void*ponteiros.

Trabalho
fonte
16
A aritmética de ponteiro +1 é definida apenas para ponteiros para tipos de objetos (completos) . voidé um tipo incompleto que nunca pode ser concluído por definição.
schot
1
@schot: Exatamente. Além disso, a aritmética do ponteiro é definida apenas em um ponteiro para um elemento de um objeto de matriz e somente se o resultado da operação for um ponteiro para um elemento nessa mesma matriz ou após o último elemento dessa matriz. Se essas condições não forem atendidas, é um comportamento indefinido. (Do padrão C99 6.5.6.8)
Trabalho
1
Aparentemente, não é assim com o gcc 7.3.0. O compilador aceita p + 1024, onde p é nulo *. E o resultado é o mesmo que ((char *) p) + 1024
zzz777
@ zzz777 é uma extensão do GCC, veja o link na parte superior da resposta votada.
Ruslan
19

converta-o em um ponteiro de char e aumente seu ponteiro para a frente x bytes à frente.

Ultimate Gobblement
fonte
4
Porque se importar? Basta convertê-lo para o tipo certo - que sempre deve ser conhecido - e incrementá-lo para 1. Converter para char, incrementar xe depois reinterpretar o novo valor como algum outro tipo é um comportamento inútil e indefinido.
Underscore_d
9
Se você está escrevendo sua função de classificação, que segundo man 3 qsortdeve ter o void qsort(void *base, size_t nmemb, size_t size, [snip]), então você tem nenhuma maneira de saber o "tipo certo"
alisianoi
15

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

Norma C11 §6.2.5

Nº 19

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

O programa a seguir está funcionando bem no compilador GCC.

#include<stdio.h>

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

Outros compiladores podem gerar um erro.

msc
fonte
14

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

Oliver Charlesworth
fonte
8

Você deve convertê-lo em outro tipo de ponteiro antes de fazer a aritmética do ponteiro.

Jakob
fonte
7

Ponteiros nulos podem apontar para qualquer pedaço de memória. Portanto, o compilador não sabe quantos bytes aumentar / diminuir quando tentamos aritmética de ponteiro em um ponteiro nulo. Portanto, os ponteiros nulos devem primeiro ser 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.
tchrist
fonte
-1

O compilador sabe por tipo de conversão. Dado um void *x:

  • x+1 adiciona um byte a x , o ponteiro vai ao bytex+1
  • (int*)x+1adiciona sizeof(int)bytes, o ponteiro vai para o bytex + sizeof(int)
  • (float*)x+1sizeof(float)bytes de endereço , etc.

Embora o primeiro item não seja portátil e seja contra o Galateo do C / C ++, ele é correto na linguagem C, o que significa que ele será compilado com algo na maioria dos compiladores que talvez exijam uma sinalização apropriada (como -Wpointer-arith)

Rytis
fonte
2
Althought the first item is not portable and is against the Galateo of C/C++Verdade. it is nevertheless C-language-correctFalso. Isso é duplo-pensamento! A aritmética do ponteiro void *é sintaticamente ilegal, não deve ser compilada e, se o fizer, produz um comportamento indefinido. Se um programador descuidado puder compilá-lo desativando algum aviso, isso não é desculpa.
Underscore_d
@underscore_d: Eu acho que alguns compiladores costumavam permitir isso como uma extensão, já que é muito mais conveniente do que ter que converter para, unsigned char*por exemplo, adicionar um sizeofvalor a um ponteiro.
Supercat