2013-03-14 11 views
5

w poniższej może ktoś wyjaśnić wyjścia uzyskuje się, gdy wykonywane:dwuwymiarowe tablice ze wskazówkami

#include<stdio.h> 
void main() 
{ 
char a[5][10]={"one","two","three","four","five"}; 
char **str=a; 
printf("%p ", &a[0]); 
printf("\n%p ", &str[0]); 
printf("\n%p ", &str[3]); 
printf("\n%p ", &str[1][56]); 
printf("\n%p ", &(*(*(str+4)+1))); 
} 

Poniżej jest wyjście zauważył:

0xbf7f6286 
0xbf7f6286 
0xbf7f6292 
0x38 
0x1 
  • & A [0] jest adres adresu początkowego tablicy
  • & str [0] jest thesame

    Czy ktoś może wyjaśnić, jak adres str [3] to 0xbf7f6292. Zgodnie z moim rozumieniem str2 [0] i & str [3] powinny różnić się o 30 bajtów.

    Proszę również wyjaśnić innym, w jaki sposób dane wyjściowe również dla dwóch pozostałych przypadków.

z góry dzięki

+0

macierzy 'A', nawet z dwóch wymiarów, nie jest równoznaczne z wskaźnik do wskaźnika. – WhozCraig

Odpowiedz

3

pierwsze dwa printf są oczywiście drukuje ten sam adres, to znaczy pierwszy elememt dwa-d tablicy, który jest jednym d tablicy. więc nie powinno być wątpliwości, że oba z nich wskazują początek tablicy, czyli [0] [0].

char ** str jest wskaźnikiem, więc jeśli zrobisz str [3] to znaczy internaly (str + 3) i najprawdopodobniej twój kod działa na maszynie 32-bitowej, więc przeskoczy do 12 bajtów przed nami jak na wskaźnik airthematic. nie mylić z tablicą, str jest wskaźnikiem 2-d, a nie tablicą.

0xbf7f6286 str [0] 0xbf7f6292 str [3] diff jest 0xc, który jest poprawny zgodnie z oczekiwaniami.

printf ("\ n% p", & str [1] [56]); printf ("\ n% p", & (( (str + 4) +1)));

Powyżej dwóch ostatnich stwierdzeń, które próbowano wydrukować z powodu niewłaściwego podkreślenia wskaźnika airthematic.

Mam nadzieję, że pomogłoby to rozwiązać wątpliwości.

+0

Mam wątpliwości: ponieważ jest to tablica 2D, wskazuje adres początkowy tablicy 2D. Ale, str jest wskaźnikiem do wskaźnika. Podczas kopiowania str = a tak wskaźnik zmiennej str zawiera adres a i wskaźnik zmienna str zostanie utworzona w pewnej lokalizacji "x". więc kiedy drukujemy i str [0] powinno wypisywać "x" zamiast adresu prawa? Proszę, rozwiąż jeśli coś jest nie tak – user2165725

0

Kiedy powiesz str + 3, oznacza to str + (3 * sizeof (char **)). Więc jeśli rozmiar "char **" wynosi 4 bajty, wówczas liczba str. jest zwiększana o 12 bajtów.

0

Jeśli chcesz wydrukować tablicę 2D, powinieneś używać dwóch pętli (sugerowałbym dwie pętle "dla") i jedną zagnieżdżoną w drugiej.

Jeśli chodzi o dane wyjściowe, to (nie chcę podać oczywistych) wszystkich adresów, ponieważ po wpisaniu "&" przed uzyskaniem dostępu do elementu tablicy, pytasz o jego adres.

Ponieważ ustawiasz tablicę a równą str, tak naprawdę ustawiasz wskaźnik na adres, do którego wskazuje tablica "a".

Kiedy używasz [int n], operatora dostępu, faktycznie mówisz swojemu komputerowi, aby szukał 'n' gniazd adresowych za którymi faktycznie wskazuje tablica (lub wskaźnik). Więc dlaczego jest 16 więcej niż dwóch pierwszych.

Dwa ostatnie wyglądają jak Hex, nie jestem całkowicie pewien tych ostatnich dwóch linii wyjściowych.

1
char a[5][10]={"one","two","three","four","five"}; 
char **str=a; 

Pozwól mi wyjaśnić, w jaki sposób dwie prace wskaźnika start, i jaki jest jej związek z tablicy, tak że można mieć większą szansę próbując rozwiązać problem samodzielnie.

Konwencjonalnie podwójne wskaźniki gwiazdy są przypisywane "adresowi" innego wskaźnika.

Przykład

char a = 5; 
char *b = &a; 
char **c = &b; 

więc gdy de-reference c jednorazowo

printf("\n%d",*c) 

, dostaniesz wartość zawartych przez 'b', która jest niczym innym adresem 'a' .

Po de-reference c dwa razy

printf("\n%d",**c) 

, otrzymasz wartość 'a' bezpośrednio.

Teraz dochodzimy do relacji między tablicami i wskaźnikami.

Nazwa tablicy jest synonimem jej adresu, co oznacza, że ​​nazwa tablicy jest sama w sobie.

Przykład:

int array [5] = {1,2,3,4,5}; 
printf("\n%p",array); 
printf("\n%p",&array); 
printf("\n%p",&array[0]); 

wszystkie z nich będzie drukować ten sam adres.

Stąd jest nieprawidłowy Przydzielenie ruchy wskaźnika

char **str=a; 

Ponieważ ** ul oczekuje adresu wskaźnika posiadającej adres, ale są po prostu przechodząc tylko adres [nazwa tablicy = A].

Krótko mówiąc, wskaźnik dwugwiazdkowy nie powinien być używany do obsługi dwuwymiarowej tablicy.

Ten question zajmuje się obsługą dwuwymiarowej tablicy ze wskaźnikiem.

8

bardzo krótkiej odpowiedzi:

Zmień to:

char **str; 

Aby to być:

char (*str)[10]; 

i naprawić ewentualne błędy w kodzie później. Typ str nie jest zgodny z typem twojej dwuwymiarowej tablicy. Pozwoliłem sobie na ustalenie typu zwrotu na int dla main() (pustka nie jest dozwolona zgodnie ze standardem C).Ja również zaznaczone źródło gdzie technicznie mają niezdefiniowanej zachowanie:

#include <stdio.h> 
int main() 
{ 
    char a[5][10]={"one","two","three","four","five"}; 
    char (*str)[10] = a; 
    printf("%p ", &a[0]); 
    printf("\n%p ", &str[0]); 
    printf("\n%p ", &str[3]); 
    printf("\n%p ", &str[1][56]);  // this is undefined behaviour 
    printf("\n%p ", &(*(*(str+4)+1))); 
    return 0; 
} 

Należy również pamiętać, że ostatni printf() oświadczenie ma zbędny &(*(...)) że nie jest potrzebne. To może być usunięte, aby wyglądać tak, co jest równoważne:

printf("\n%p ", *(str+4)+1); 

wyjściowa (zależne od systemu)

0x7fff5fbff900 
0x7fff5fbff900 
0x7fff5fbff91e 
0x7fff5fbff942 
0x7fff5fbff929 

The Very (Very, Bardzo) długa odpowiedź

Niepoprawnie przyjmujecie założenie, że dwa wymiary al tablica jest równoważna z wskaźnikiem do wskaźnika. Są one podobne tylko w składni użycia. Poniżej znajduje się szorstki układ jak tablica a jest przedstawiana w pamięci

char a[5][10] 

    0 1 2 3 4 5 6 7 8 9 
    ----------------------------------------- 
0 | o | n | e | 0 | | | | | | | 
    ----------------------------------------- 
1 | t | w | o | 0 | | | | | | | 
    ----------------------------------------- 
2 | t | h | r | e | e | 0 | | | | | 
    ----------------------------------------- 
3 | f | o | u | r | 0 | | | | | | 
    ----------------------------------------- 
4 | f | i | v | e | 0 | | | | | | 
    ----------------------------------------- 

Należy pamiętać, że adres początkowy drugiego rzędu, co jest &a[1], jest dziesięć bajtów przeszłość adres początkowy z pierwszego rzędu, &a[0] (co nie jest przypadkowe początkowym adresem całej tablicy). Dodaje się pokazuje to:

int main() 
{ 
    char a[5][10] = { "one", "two", "three", "four", "five" }; 

    printf("&a = %p\n", &a); 
    printf("&a[0] = %p\n", &a[0]); 
    printf("a[0] = %p\n", a[0]); 
    printf("&a[1] = %p\n", &a[1]); 
    printf("a[1] = %p\n", a[1]); 
    return 0; 
} 

Wyjście

&a = 0x7fff5fbff8e0 
&a[0] = 0x7fff5fbff8e0 
a[0] = 0x7fff5fbff8e0 
&a[1] = 0x7fff5fbff8ea 
a[1] = 0x7fff5fbff8ea 

Należy pamiętać, że adres na a[1] wynosi 10 bajty (0x0a hex) Wcześniejsze początku tablicy. Ale dlaczego? Aby odpowiedzieć na to pytanie, należy zrozumieć podstawy arytmetyki wskazywanych wskaźników.


Jak działa arytmetyczna praca wskaźnikowa?

W językach C i C++ wszystkie wskaźniki oprócz void są wpisywane. Ma podstawowy typ danych powiązany ze wskaźnikiem. Na przykład.

int *iptr = malloc(5*sizeof(int)); 

iptr powyższe punkty do obszaru pamięci przydzielonej być wielkości pięciu liczb całkowitych. Zwracając się go jak zwykle dotyczy tablic wygląda następująco:

iptr[1] = 1; 

ale możemy tak łatwo rozwiązać to tak:

*(iptr+1) = 1; 

a wynik byłby taki sam; przechowywanie wartości 1 w drugim gnieździe macierzowym (0-slot jest pierwszym). Wiemy, że operator dereferencji * umożliwia dostęp przez adres (adres zapisany w wskaźniku). Ale w jaki sposób (iptr+1) wie, aby pominąć cztery bajty (jeśli twoje typy int są 32-bitowe, osiem bajtów, jeśli są 64-bitowe), aby uzyskać dostęp do następnego przedziału liczb całkowitych?

Odpowiedź: ponieważ arytmetyczna wskazówka.Kompilator wie, jak szeroki w bajtach jest wskaźnik typu (w tym przypadku szerokość typu int). Gdy dodajesz lub odejmujesz wartości skalera do/od wskaźników, kompilator generuje odpowiedni kod, aby uwzględnić tę "szerokość typu". Działa również z typami użytkowników. To pokazano poniżej:

#include <stdio.h> 

typedef struct Data 
{ 
    int ival; 
    float fval; 
    char buffer[100]; 
} Data; 

int main() 
{ 
    int ivals[10]; 
    int *iptr = ivals; 
    char str[] = "Message"; 
    char *pchr = str; 
    Data data[2]; 
    Data *dptr = data; 

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

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

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

    return 0; 
} 

wyjścia

iptr = 0x7fff5fbff900 
iptr+1 = 0x7fff5fbff904 
pchr = 0x7fff5fbff8f0 
pchr+1 = 0x7fff5fbff8f1 
dptr = 0x7fff5fbff810 
dptr+1 = 0x7fff5fbff87c 

zauważyć różnicę między adresem iptr i iptr+1 jest nie jeden bajt; to jest cztery bajtów (szerokość int w moim systemie). Następnie szerokość pojedynczego char została zademonstrowana jako pchr i pchr+1 jako jeden bajt. Na koniec nasz niestandardowy typ danych Data z dwoma wartościami wskaźnika: dptr i dptr+1 wskazuje, że ma on szerokość 0x6C lub 108 bajtów. (Mogło być większe ze względu na strukturę opakowania i wyrównanie pola, ale mieliśmy szczęście, że ten przykład nie był). Ma to sens, ponieważ struktura zawiera dwa 4-bajtowe pola danych (int i float) oraz bufor znaków o szerokości 100 elementów.

Przy okazji jest też odwrotnie, a często jest to , a nie rozpatrywany przez nawet doświadczonych programistów C/C++. Jest to wskaźnik o typie różnicujący. Jeśli masz dwie ważne wskazówki dotyczące określonego typu w ciągłym obszarze pamięci ważnego:

int ar[10]; 
int *iptr1 = ar+1; 
int *iptr5 = ar+5; 

Co myślisz, można uzyskać w wyniku od:

printf("%lu", iptr5 - iptr1); 

Odpowiedź jest .. 4. Whoopee, mówisz. Nic takiego? Nie wierzysz w to. Jest to niezwykle przydatne przy używaniu arytmetyki wskaźnikowej do określania przesunięcia w buforze określonego elementu.

Podsumowując, jeśli masz wyrażenie takiego:

int ar[5]; 
int *iptr = ar; 

iptr[1] = 1; 

Można go znać odpowiada:

*(iptr+1) = 1; 

, które pod względem lay-człowieka, czyli „Take adres przechowywany w zmiennej iptr, dodaj do niej 1 * (szerokość bajtów int w bajtach), a następnie zapisz wartość 1 w pamięci dereferencji przez zwracany adres. "

Pasek strony: To też by działało.Sprawdź, czy możesz myśleć dlaczego

1[iptr] = 1; 

Powrót do (naszego) tablicy próbki, teraz spojrzeć na to, co się dzieje, gdy mówimy o tym samym adresie a, ale dzięki podwójnym wskaźnikiem (co jest całkowicie niepoprawne, a Twój kompilator powinien przynajmniej ostrzec cię o przypisaniu):

char **str = a; // Error: Incompatible pointer types: char ** and char[5][10] 

Cóż, to nie działa. Ale załóżmy przez chwilę, że tak. char ** to wskaźnik do wskaźnika do znaku. Oznacza to, że sama zmienna zawiera jedynie wskaźnik. Nie ma koncepcji szerokości wiersza bazowego itp. Zakładając, że wpisujesz adres a w podwójnym wskaźniku str.

char **str = (char **)(a); // Should NEVER do this, here for demonstration only. 
char *s0 = str[0]; // what do you suppose this is? 

Lekko aktualizacja do naszego programu testowego:

int main() 
{ 
    char a[5][10] = { "one", "two", "three", "four", "five" }; 
    char **str = (char **)a; 
    char *s0 = str[0]; 
    char *s1 = str[1]; 

    printf("&a = %p\n", &a); 
    printf("&a[0] = %p\n", &a[0]); 
    printf("a[0] = %p\n", a[0]); 
    printf("&a[1] = %p\n", &a[1]); 
    printf("a[1] = %p\n", a[1]); 

    printf("str = %p\n", str); 
    printf("s0 = %p\n", s0); 
    printf("s1 = %p\n", s1); 

    return 0; 
} 

daje nam następujący wynik:

&a = 0x7fff5fbff900 
&a[0] = 0x7fff5fbff900 
a[0] = 0x7fff5fbff900 
&a[1] = 0x7fff5fbff90a 
a[1] = 0x7fff5fbff90a 
str = 0x7fff5fbff900 
s0 = 0x656e6f 
s1 = 0x6f77740000 

dobrze, str wygląda to, co chcemy, ale to, co jest rzeczą w s0? Dlaczego, to są wartości znaków ASCII dla liter. Które? Szybkie sprawdzenie godnej ASCII table pokazuje to:

0x65 : e 
0x6e : n 
0x6f : o 

Ów słowo „jeden” w odwrotnej (reverse jest spowodowana przez obsługę endian wartości multi-bajtowych w moim systemie, ale mam nadzieję, że problem jest Co oczywiste, o tej drugiej wartości.

0x6f : o 
0x77 : w 
0x74 : t 

tak, to jest to „dwa” Dlaczego więc coraz bajtów w naszej tablicy jako wskaźniki

Hmmm .. tak, jak powiedziałem w.? mój komentarz, ktokolwiek powiedział ci albo hin dla ciebie, że podwójne wskaźniki i dwuwymiarowe tablice są synonimami, były nieprawidłowe. Przypomnijmy naszą przerwę na arytmetykę wskazywanych wskaźników. Pamiętaj, że:

str[1] 

i tak:

*(str+1) 

są synonimami. Dobrze. co to jest typ wskaźnika str? Typ, który wskazuje, to wskaźnik char. w związku z tym, różnica pomiędzy tym liczba bajtów:

str + 0 

i to

str + 1 

będzie wielkość char*. W moim systemie 4 bajty (mam 32-bitowe wskaźniki). Wyjaśnia to, dlaczego widoczny adres w czterech bajtach w bazie naszej oryginalnej tablicy.

Dlatego odpowiadając na twoje pierwsze podstawowe pytanie (tak, my wreszcie do tego dojdziemy).

Dlaczego str[3] jest 0xbf7f6292

Odpowiedź::

&str[3] 

jest odpowiednikiem tego:

(str + 3) 

Ale wiemy z góry, że (str + 3) jest tylko adres przechowywane w str, następnie dodajemy 3x szerokość typu str punktów, co oznacza char *, w bajtach na ten adres. Dobrze. wiemy, ze swoim drugim printf co to adres:

0xbf7f6286 

Wiemy szerokość wskaźnika w systemie wynosi 4 bajty (32 bity pointers). Dlatego ...

0xbf7f6286 + (3 * 4) 

lub ....

0xbf7f6286 + 0x0C = 0xbf7f6292 
+1

niesamowita odpowiedź !! OP musiał mieć świetny dzień, aby znaleźć taką odpowiedź, będę faworytem, ​​to pytanie, tylko dla tego postu !!! –

+0

Doskonała odpowiedź ... Naprawdę pomocna – user2165725

+0

@ user2165725, jeśli uważasz, że jakakolwiek odpowiedź jest przydatna, mówisz tak, akceptując odpowiedź –

Powiązane problemy