Arytmetyka wskaźnika dla void pointer w C


176

Kiedy wskaźnik do określonego typu (powiedzmy int, char, float, ..) jest zwiększany, jego wartość jest zwiększana o wielkości tego typu danych. Jeśli voidwskaźnik, który wskazuje na dane o rozmiarze, xjest zwiększany, w jaki sposób ma wskazywać xbajty do przodu? Skąd kompilator wie, aby dodać xdo wartości wskaźnika?



3
Pytanie brzmi tak, jakby zakładało, że kompilator (/ czas wykonywania) wie, na jaki typ obiektu został ustawiony wskaźnik, i dodaje jego rozmiar do wskaźnika. To kompletne nieporozumienie: zna tylko adres.
PJTraill

2
„Jeśli voidwskaźnik wskazujący na dane o rozmiarze xjest zwiększany, w jaki sposób ma on wskazywać xbajty do przodu?” Tak nie jest. Dlaczego ludzie, którzy mają takie pytania, nie mogą przetestować ich przed zapytaniem - wiesz, przynajmniej do absolutnego minimum, gdzie sprawdzają, czy faktycznie się kompiluje, a tak nie jest. -1, nie mogę uwierzyć, że to ma +100 i -0.
underscore_d

Odpowiedzi:


287

Końcowy wniosek: arytmetyka na a void*jest niedozwolona zarówno w C, jak i C ++.

GCC dopuszcza to jako rozszerzenie, patrz Arytmetyka na void- i wskaźnikach funkcji (zwróć uwagę, że ta sekcja jest częścią rozdziału „Rozszerzenia C” podręcznika). Clang i ICC prawdopodobnie zezwalają na void*arytmetykę dla celów zgodności z GCC. Inne kompilatory (takie jak MSVC) nie zezwalają na arytmetykę void*, a GCC nie zezwala na to, jeśli -pedantic-errorsflaga jest określona lub -Werror-pointer-arithflaga jest określona (ta flaga jest przydatna, jeśli baza kodu musi również kompilować się z MSVC).

Mówi standard C.

Cytaty pochodzą z projektu n1256.

Standardowy opis operacji dodawania stwierdza:

6.5.6-2: W celu dodania oba operandy będą miały typ arytmetyczny lub jeden operand będzie wskaźnikiem do typu obiektu, a drugi będzie typu całkowitego.

Zatem pytanie tutaj brzmi, czy void*jest to wskaźnik do „typu obiektu”, czy też równoważnie, czy voidjest to „typ obiektu”. Definicja „typu obiektu” to:

6.2.5.1: Typy są podzielone na typy obiektów (typy, które w pełni opisują obiekty), typy funkcji (typy opisujące funkcje) i typy niekompletne (typy, które opisują obiekty, ale brakuje im informacji potrzebnych do określenia ich rozmiarów).

A standard definiuje voidjako:

6.2.5-19: voidtyp zawiera pusty zestaw wartości; jest to niekompletny typ, którego nie można ukończyć.

Ponieważ voidjest to niekompletny typ, nie jest to typ obiektowy. Dlatego nie jest poprawnym operandem do operacji dodawania.

Dlatego nie można wykonywać arytmetyki wskaźnika na voidwskaźniku.

Uwagi

Początkowo sądzono, że void*arytmetyka jest dozwolona ze względu na następujące sekcje normy C:

6.2.5-27: Wskaźnik do void powinien mieć takie same wymagania dotyczące reprezentacji i wyrównania , jak wskaźnik do typu znaku.

Jednak,

Te same wymagania dotyczące reprezentacji i wyrównania mają implikować zamienność jako argumenty funkcji, wartości zwracane z funkcji i członków związków.

Oznacza to, że printf("%s", x)ma to samo znaczenie, niezależnie xod tego, czy ma typ, char*czy void*, ale nie oznacza to, że możesz wykonywać działania arytmetyczne na void*.

Uwaga redaktora: ta odpowiedź została zredagowana, aby odzwierciedlić ostateczny wniosek.


7
Ze standardu C99: (6.5.6.2) Dodatkowo albo oba operandy będą miały typ arytmetyczny, albo jeden operand będzie wskaźnikiem do typu obiektu, a drugi będzie miał typ całkowity. (6.2.5.19) Typ void zawiera pusty zbiór wartości; jest to niekompletny typ, którego nie można ukończyć. Myślę, że to wyjaśnia, że void*arytmetyka wskaźników jest niedozwolona. GCC ma rozszerzenie, które to umożliwia.
Job

1
Jeśli uważasz, że Twoja odpowiedź nie jest już przydatna, możesz ją po prostu usunąć.
kawiarnia

1
Ta odpowiedź była przydatna, mimo że okazała się błędna, ponieważ zawiera ostateczny dowód na to, że wskaźniki do pustki nie są przeznaczone do arytmetyki.
Ben Flynn,

1
To jest dobra odpowiedź, ma właściwe wnioski i niezbędne cytaty, ale ludzie, którzy przyszli na to pytanie, doszli do złego wniosku, ponieważ nie przeczytali do końca odpowiedzi. Zredagowałem to, aby było bardziej oczywiste.
Dietrich Epp

1
Clang i ICC nie pozwalają na void*arytmetykę (przynajmniej domyślnie).
Sergey Podobry

61

Arytmetyka wskaźników nie jest dozwolona w przypadku void*wskaźników.


16
+1 Arytmetyka wskaźnika jest zdefiniowana tylko dla wskaźników (kompletnych) typów obiektów . voidjest niekompletnym typem , którego z definicji nigdy nie można ukończyć.
schot

1
@schot: Dokładnie. Co więcej, arytmetyka wskaźnika jest zdefiniowana tylko na wskaźniku do elementu obiektu tablicy i tylko wtedy, gdy wynikiem operacji byłby wskaźnik do elementu w tej samej tablicy lub jeden za ostatnim elementem tej tablicy. Jeśli te warunki nie są spełnione, jest to zachowanie niezdefiniowane. (Ze standardu C99 6.5.6.8)
Job

1
Najwyraźniej tak nie jest w przypadku gcc 7.3.0. Kompilator akceptuje p + 1024, gdzie p jest nieważne *. Wynik jest taki sam jak ((char *) p) + 1024
zzz777

@ zzz777 to rozszerzenie GCC, zobacz link w odpowiedzi najczęściej głosowanej.
Ruslan

19

rzuć go na wskaźnik typu char i zwiększ swój wskaźnik do przodu o x bajtów do przodu.


4
Po co się męczyć? Po prostu przerzuć go na właściwy typ - który zawsze powinien być znany - i zwiększ go o 1. Rzutowanie na char, zwiększanie o x, a następnie reinterpretacja nowej wartości jako innego typu jest zarówno bezcelowym, jak i niezdefiniowanym zachowaniem.
underscore_d

9
Jeśli piszesz swoją funkcję sortowania, która zgodnie z man 3 qsortpowinna mieć void qsort(void *base, size_t nmemb, size_t size, [snip]), to nie masz możliwości poznania „właściwego typu”
alisianoi

15

Standard C nie zezwala na arytmetykę wskaźnika void . Jednak GNU C jest dozwolony przez biorąc pod uwagę wielkość pustych Is 1.

Standard C11 §6.2.5

Akapit - 19

voidTypu zawiera pustą zestaw wartości; jest to niekompletny typ obiektu , którego nie można ukończyć.

Poniższy program działa dobrze w kompilatorze 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;
}

Być może inne kompilatory generują błąd.



8

Musisz rzucić to na inny typ wskaźnika przed wykonaniem arytmetyki wskaźnika.


7

Wskaźniki pustki mogą wskazywać na dowolny fragment pamięci. Dlatego kompilator nie wie, ile bajtów należy zwiększyć / zmniejszyć, gdy próbujemy wykonać arytmetykę wskaźnika na pustym wskaźniku. Dlatego wskaźniki void muszą być najpierw rzutowane na znany typ, zanim będą mogły zostać włączone do jakiejkolwiek arytmetyki wskaźników.

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.

-1

Kompilator rozpoznaje rzutowanie typu. Biorąc pod uwagę void *x:

  • x+1dodaje jeden bajt do x, wskaźnik przechodzi do bajtux+1
  • (int*)x+1dodaje sizeof(int)bajty, wskaźnik przechodzi do bajtux + sizeof(int)
  • (float*)x+1adresy sizeof(float)bajty itp.

Chociaż pierwszy element nie jest przenośny i jest sprzeczny z Galateo z C / C ++, niemniej jednak jest poprawny w języku C, co oznacza, że ​​będzie się kompilował do czegoś na większości kompilatorów, prawdopodobnie wymagających odpowiedniej flagi (jak -Wpointer-arith)


2
Althought the first item is not portable and is against the Galateo of C/C++Prawdziwe. it is nevertheless C-language-correctFałszywe. To podwójne myślenie! Arytmetyka wskaźnika na void *jest składniowo niedozwolona, ​​nie powinna się kompilować, a jeśli tak, to powoduje niezdefiniowane zachowanie. Jeśli nieostrożny programista może go skompilować, wyłączając niektóre ostrzeżenia, to nie jest wymówka.
underscore_d

@underscore_d: Myślę, że niektóre kompilatory zezwalały na to jako rozszerzenie, ponieważ jest to o wiele wygodniejsze niż rzutowanie unsigned char*na np. dodanie sizeofwartości do wskaźnika.
supercat
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.