int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
powoduje segfault, podczas gdy
int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
nie. Teraz:
int *nums = {5, 2, 1, 4};
printf("%d\n", nums);
wydruki 5.
Opierając się na tym, przypuszczałem, że notacja inicjalizacji tablicy {} ślepo ładuje te dane do dowolnej zmiennej po lewej stronie. Gdy jest to int [], tablica jest wypełniana zgodnie z potrzebami. Kiedy jest to int *, wskaźnik jest zapełniany przez 5, a lokalizacje pamięci po miejscu, w którym jest przechowywany wskaźnik, są wypełniane o 2, 1 i 4. So nums [0] próbuje usunąć 5, powodując segfault.
Jeśli się mylę, popraw mnie. A jeśli mam rację, proszę o wyjaśnienie, ponieważ nie rozumiem, dlaczego inicjatory tablic działają tak, jak działają.
-pedantic-errors
flagą i obserwuj diagnostykę.int *nums = {5, 2, 1, 4};
nie jest ważny C.Odpowiedzi:
W języku C istnieje (głupia) reguła mówiąca, że każda zwykła zmienna może być zainicjowana listą inicjalizującą w nawiasach klamrowych, tak jakby była tablicą.
Na przykład możesz napisać
int x = {0};
, co jest całkowicie równoważne zint x = 0;
.Więc kiedy piszesz
int *nums = {5, 2, 1, 4};
, w rzeczywistości podajesz listę inicjalizującą pojedynczej zmiennej wskaźnikowej. Jednak jest to tylko jedna zmienna, więc zostanie przypisana tylko pierwsza wartość 5, reszta listy jest ignorowana (właściwie nie sądzę, aby kod z nadmiarem inicjalizatorów powinien się nawet kompilować za pomocą ścisłego kompilatora) - tak nie jest w ogóle zostać zapisane w pamięci. Kod jest równoważny zint *nums = 5;
. Co oznacza, żenums
powinien wskazywać na adres5
.W tym momencie powinieneś już otrzymać dwa ostrzeżenia / błędy kompilatora:
A potem oczywiście kod ulegnie awarii i wypali się, ponieważ
5
najprawdopodobniej nie jest to prawidłowy adres, do którego można się odwołaćnums[0]
.Na marginesie, powinieneś
printf
wskazać adresy ze%p
specyfikatorem lub w inny sposób wywołać niezdefiniowane zachowanie.Nie jestem do końca pewien, co próbujesz tutaj zrobić, ale jeśli chcesz ustawić wskaźnik tak, aby wskazywał na tablicę, powinieneś zrobić:
int nums[] = {5, 2, 1, 4}; int* ptr = nums; // or equivalent: int* ptr = (int[]){5, 2, 1, 4};
Lub jeśli chcesz utworzyć tablicę wskaźników:
int* ptr[] = { /* whatever makes sense here */ };
EDYTOWAĆ
Po kilku badaniach mogę powiedzieć, że „lista inicjalizująca nadmiarowe elementy” rzeczywiście nie jest poprawna. C - jest to rozszerzenie GCC .
Standard 6.7.9 Inicjalizacja mówi (moje podkreślenie):
„Typ skalarny” jest standardowym terminem odnoszącym się do pojedynczych zmiennych, które nie są typu tablicowego, strukturalnego lub sumarycznego (są to nazywane „typami agregującymi”).
Tak więc w prostym języku angielskim standard mówi: „kiedy inicjalizujesz zmienną, możesz dodać kilka dodatkowych nawiasów klamrowych wokół wyrażenia inicjującego, tylko dlatego, że możesz”.
źródło
{}
. Wręcz przeciwnie, ułatwia jeden z najważniejszych i wygodnych idiomów języka C -{ 0 }
jako uniwersalny inicjator zera. Wszystko w C może być inicjalizowane przez zero= { 0 }
. Jest to bardzo ważne przy pisaniu kodu niezależnego od typu.{0}
oznacza jedynie inicjalizację pierwszego obiektu do zera i inicjalizację pozostałych obiektów tak, jakby miały statyczny czas trwania. Powiedziałbym, że jest to raczej przypadek niż celowy projekt języka jakiegoś "uniwersalnego inicjatora", ponieważ{1}
nie inicjalizuje wszystkich obiektów na 1.p = 5;
(żaden z wymienionych przypadków nie jest spełniony dla przypisania liczby całkowitej do wskaźnika); a 6.7.9 / 11 mówi, że ograniczenia przypisania są również używane do inicjalizacji.{}
w tym celu dopuszcza się inicjalizację skalarów. Jedyną rzeczą, która ma znaczenie, jest to, że= { 0 }
inicjalizator gwarantuje zerową inicjalizację całego obiektu , co właśnie uczyniło go klasycznym i jednym z najbardziej eleganckich idiomów języka C.{1}
ma wspólnego z tematem. Nikt nigdy nie twierdził, że{0}
interpretuje to0
jako wielokrotną inicjalizację dla każdego członka agregatu.SCENARIUSZ 1
Dlaczego ten się rozbija?
Zadeklarowałeś
nums
jako wskaźnik do int -nums
to ma zawierać adres one liczby całkowitej w pamięci.Następnie próbowałeś zainicjować
nums
tablicę wielu wartości. Tak więc bez zagłębiania się w wiele szczegółów jest to koncepcyjnie niepoprawne - nie ma sensu przypisywanie wielu wartości zmiennej, która ma mieć jedną wartość. Pod tym względem zobaczysz dokładnie ten sam efekt, jeśli zrobisz to:int nums = {5, 2, 1, 4}; // <-- assign multiple values to an int variable printf("%d\n", nums); // also print 5
W każdym przypadku (przypisz wiele wartości do wskaźnika lub zmiennej int), wtedy zmienna otrzyma pierwszą wartość, która jest
5
, a pozostałe wartości są ignorowane. Ten kod jest zgodny, ale otrzymasz ostrzeżenia dla każdej dodatkowej wartości, która nie powinna znajdować się w przypisaniu:warning: excess elements in scalar initializer
.W przypadku przypisywania wielu wartości do zmiennej wskaźnikowej, program segfaults podczas uzyskiwania dostępu
nums[0]
, co oznacza, że dosłownie odrzucasz wszystko, co jest zapisane pod adresem 5 . W tym przypadku nie przydzieliłeś żadnej prawidłowej pamięci dla wskaźnikanums
.Warto zauważyć, że nie ma segfaulta w przypadku przypisania wielu wartości do zmiennej int (nie wyłuskujemy tutaj żadnego nieprawidłowego wskaźnika).
SCENARIUSZ 2
int nums[] = {5, 2, 1, 4};
Ten nie powoduje segfaultów, ponieważ zgodnie z prawem przydzielasz tablicę 4 int w stosie.
SCENARIUSZ 3
int *nums = {5, 2, 1, 4}; printf("%d\n", nums); // print 5
Ten nie działa zgodnie z oczekiwaniami, ponieważ drukujesz wartość samego wskaźnika - NIE tego, co wyłuskuje (co jest nieprawidłowym dostępem do pamięci).
Inni
Prawie zawsze jest skazane na segfault za każdym razem, gdy
zakodujesz wartość takiego wskaźnika(ponieważ zadaniem systemu operacyjnego jest określenie, który proces może uzyskać dostęp do jakiej lokalizacji pamięci).int *nums = 5; // <-- segfault
Tak więc ogólną zasadą jest, aby zawsze inicjalizować wskaźnik do adresu jakiejś przydzielonej zmiennej, takiej jak:
int a; int *nums = &a;
lub,
int a[] = {5, 2, 1, 4}; int *nums = a;
źródło
int *nums = {5, 2, 1, 4};
jest źle sformułowanym kodem. Istnieje rozszerzenie GCC, które traktuje ten kod tak samo, jak:int *nums = (int *)5;
próba utworzenia wskaźnika do adresu pamięci 5. (Nie wydaje mi się to przydatne rozszerzenie, ale wydaje mi się, że programiści tego chcą).
Aby uniknąć tego zachowania (lub przynajmniej otrzymać ostrzeżenie), możesz skompilować w trybie standardowym, np
-std=c11 -pedantic
.Alternatywną formą prawidłowego kodu byłoby:
int *nums = (int[]){5, 2, 1, 4};
co wskazuje na zmienny literał o takim samym czasie przechowywania jak
nums
. Jednakint nums[]
wersja jest ogólnie lepsza, ponieważ zajmuje mniej miejsca i można jej użyćsizeof
do wykrycia długości tablicy.źródło
nums
?nums
jest to zmienna statyczna zadeklarowana w funkcji, czy też kompilator byłby uprawniony do ograniczenia czasu życia tablicy do czasu otaczającego bloku, nawet jeśli byłby przypisany do zmiennej statycznej?int *nums = {5, 2, 1, 4};
nums
jest wskaźnikiem typuint
. Więc powinieneś wskazać na jakąś prawidłową lokalizację pamięci.num[0]
próbujesz wyłuskać jakąś losową lokalizację pamięci i stąd błąd segmentacji.Tak, wskaźnik ma wartość 5 i próbujesz usunąć z niej odwołanie, co jest niezdefiniowanym zachowaniem w Twoim systemie. (Wygląda jak
5
to, że nie jest to prawidłowa lokalizacja pamięci w twoim systemie)Natomiast
int nums[] = {1,2,3,4};
jest prawidłową deklaracją, w której mówisz, że
nums
jest tablicą typu,int
a pamięć jest przydzielana na podstawie liczby elementów przekazanych podczas inicjalizacji.źródło
int *nums = (int[]){5, 2, 1, 4};
Przypisując
{5, 2, 1, 4}
int *nums = {5, 2, 1, 4};
przypisujesz 5 do
nums
(po niejawnym rzutowaniu z int na wskaźnik do int). Po usunięciu wywołania wywołanie dostępu do lokalizacji pamięci pod adresem0x5
. To może nie być dozwolone dla twojego programu.Próbować
printf("%p", (void *)nums);
źródło