2013-03-05 17 views
5

Myślałem, że w gcc, void * i char * są traktowane w ten sam sposób, jeśli chodzi o arytmetyki wskaźników, tj void * „punkty” do pojedynczego bajtu w pamięci, więc następujący kodC void wskaźnik arytmetyka

void *p; 
p = malloc(sizeof(void)); 
printf("%p %p\n",p,p+1); 

rzeczywiście zwraca 0x984a008 0x984a009. Podobnie pustych ** wskazuje wskaźnik tak Przyrost jedna rzeczywiście oznacza przyrost o 4 bajty (32 bitów na systemie operacyjnym), to

void **p; 
p = (void **) malloc(sizeof(void *)); 
printf("%p %p\n",p,p+1); 

powraca 0x984a008 0x984a00c. Jednak poniższy kod myli mi

void **p, *p1; 
p = (void **) malloc(sizeof(void *)); 
p1 = (void **) p; 
printf("%p %p\n",p1,p1+1); 

Ponieważ powraca ponownie 0x984a008 0x984a009. Co tu się dzieje?

+2

Może chodziło o wpisanie 'void ** p ** p1; zamiast' z 'void ** p, * p1;'? –

+1

'sizeof (void)' jest 1 lub przynajmniej daje ostrzeżenie, jest bezużyteczne. Nie rób tego. Co oznacza typ "void"? jest tylko "void *". –

+0

Mam na myśli void ** p, * p1. Wiem, że ten kod kompiluje, nie rozumiem, dlaczego jeśli p jest początkowo deklarowane jako nieważne **, p + 1 odpowiada inkrementowi o 4 bajty, podczas gdy p1 jest uznawane za nieważne *, a później przekształcone jako puste **, arytmetyka dodaje tylko 1 bajt dla p1 + 1 – Ivan

Odpowiedz

1

Nie można zwiększyć wskaźnika pustej przestrzeni. Jest to niezdefiniowane zachowanie.

Powiązane pytanie: Increment void pointer by one byte? by two?

+0

OK, ale jeśli zmienisz wszystkie typy na wskaźniki "char", pytanie nadal pozostaje (chociaż GCC da ci ostrzeżenie w w tym przypadku) – Rup

2

Podczas korzystania z void *, przyrost to 1. Podczas korzystania void **, to wielkość wskaźnika.

W operacji, która Cię myli, Twój void * rzutowany na void ** jest domyślnie zawracany do postaci void *. To tak, jakby to zrobił:

long a, b, c; 
c = a + (int) b; 

Rzucasz b do int, ale chcesz pracować z long, więc jest to odlew z powrotem.

+0

W porządku, ale dlaczego kompilator ponownie przetwarza wersję? Zarówno 'void *' jak i 'void **' pobierają 4 bajty na stosie i o ile rozumiem wszystkie arytmetyczne wskaźniki są wykonywane w czasie kompilacji. Dlaczego kompilator nalega, aby p1 został odesłany? – Ivan

+0

Pomyśl o tym w ten sposób: sensownie jest rzucić 'void **' na 'void *' - dowolny wskaźnik może stać się "void *". Z drugiej strony, odradzając, nie dawaj. Nie wszystkie wskaźniki są podwójnymi wskaźnikami. Kompilator robi wrażliwą rzecz z operacją, o którą prosiłeś: ma to sens w jedyny możliwy sposób. – slezica

7

Pomijając ewentualne niezdefiniowanej zachowanie void wskaźnik arytmetycznych na chwilę ...

Rodzaj p1 jest void *.

Nie można zmienić typu zmiennej, przypisując do niej wartość innego typu. p1 zawsze pozostanie void *.

Każde wyrażenie innego przypisanego do niego typu zostanie domyślnie przeniesione na void * (lub jeśli wystąpi błąd).

W ten sposób jest zasadniczo taki sam jak pierwszy przykład.

EDIT:

O ile mi wiadomo, rzucając z jednego typu na inny wskaźnik faktycznie nie robić nic, a jego głównym celem jest dla typu kontroli.

Wskaźnik jest tylko adres pamięci, szereg, więc zasadniczo pamięć wygląda mniej więcej tak: (post-przypisania)

p1  p2 
void * void** <- these types are fixed and known during compilation 
------ ------ 
|1234| |1234|   at address 1234 = the 4 bytes from malloc 
------ ------ 
^
    | 
this value is the only thing that will change by assigning p1 to a different value 
+3

W niektórych architekturach rzutowanie z jednego typu wskaźnika na inny zmienia coś. Na typowej architekturze x86 (_64) można jednak oczekiwać, że wszystkie wskaźniki będą miały tę samą reprezentację, a rzuty będą nieobsługiwane. –

3

Należy użyć char * zamiast void *, od arytmetyki na wskaźnik do void jest rozszerzenie gcc.

char *p1 = /* ... */; 

printf("%p %p\n", p1, p1+1); 

Jakiekolwiek punkty p, arytmetyka wskaźnik na p wykorzystuje char * typ (nie char **).

Jeśli piszesz:

char *p1 = /* ... */; 

printf("%p %p\n", p1, (char**)p1+1); 

Pointer arytmetyka wykorzystuje char **.

0

Wiem, że publikuję rok później, ale zdarzyło się to pytanie i zaintrygowało mnie to.

Zgadzam się z @Dukeling, że nie można zmienić typu zmiennej po prostu rzucając ją. Ale wydaje się, że to zależy od tego, co kompilator uważa za właściwy. Weź ten przykładowy program i spójrz na wynikowe wyjście. Zauważ, że jedyną różnicą między vp i vp2 jest część sizeof() części malloc().

sporządzenia: gcc (Debian 4.7.2-5) 4.7.2
kompilacji line: gcc -o void_test void_test.c

#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char **argv) { 
    void *vp, *vp2; 

    printf("sizeof(void) = %d\n", sizeof(void)); 
    printf("sizeof(void *) = %d\n", sizeof(void *)); 
    printf("sizeof(char) = %d\n", sizeof(char)); 
    printf("sizeof(char *) = %d\n\n", sizeof(char *)); 

    vp = (void *) malloc(sizeof(void)); 
    vp2 = (void *) malloc(sizeof(void *)); 

    printf("vp = %p\n", vp); 
    printf("vp+1 = %p\n", vp+1); 
    printf("vp2 = %p\n", vp); 
    printf("vp2+1 = %p\n", vp2+1); 

    return 0; 
} 

daje następujący wynik:

$ ./void_test 
sizeof(void) = 1 
sizeof(void *) = 8 
sizeof(char) = 1 
sizeof(char *) = 8 

vp = 0x1ee3010 
vp+1 = 0x1ee3011 
vp2 = 0x1ee3010 
vp2+1 = 0x1ee3031