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
macierzy 'A', nawet z dwóch wymiarów, nie jest równoznaczne z wskaźnik do wskaźnika. – WhozCraig