W języku C, jeśli zainicjuj tablicę w ten sposób:
int a[5] = {1,2};
wtedy wszystkie elementy tablicy, które nie zostały zainicjowane jawnie, zostaną zainicjowane niejawnie zerami.
Ale jeśli zainicjalizuję tablicę w ten sposób:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
wynik:
1 0 1 0 0
Nie rozumiem, dlaczego a[0]
drukuje 1
zamiast 0
? Czy jest to niezdefiniowane zachowanie?
Uwaga: to pytanie zostało zadane w wywiadzie.
a[2]=1
zwraca1
.a[2] = 1
jest taka1
, ale nie jestem pewien, czy wolno ci przyjąć wynik wyznaczonego wyrażenia inicjalizującego jako wartość pierwszego elementu. Fakt, że dodałeś tag prawnik, oznacza, że myślę, że potrzebujemy odpowiedzi powołującej się na standard.Odpowiedzi:
TL; DR: Nie sądzę, że zachowanie
int a[5]={a[2]=1};
jest dobrze zdefiniowane, przynajmniej w C99.Zabawne jest to, że jedynym bitem, który ma dla mnie sens, jest część, o którą pytasz:
a[0]
jest ustawiona na,1
ponieważ operator przypisania zwraca przypisaną wartość. To wszystko inne jest niejasne.Gdyby był kod
int a[5] = { [2] = 1 }
, wszystko byłoby łatwe: to jest wyznaczone ustawienie inicjalizatoraa[2]
dla1
i wszystko inne do0
. Ale{ a[2] = 1 }
mamy niewyznaczony inicjator zawierający wyrażenie przypisania i wpadamy do króliczej nory.Oto, co do tej pory znalazłem:
a
musi być zmienną lokalną.a[2] = 1
nie jest wyrażeniem stałym, więca
musi mieć automatyczne przechowywanie.a
jest objęty zakresem własnej inicjalizacji.Deklarator jest
a[5]
, więc zmienne znajdują się w zakresie w ich własnej inicjalizacji.a
żyje w swojej własnej inicjalizacji.Następuje punkt sekwencji
a[2]=1
.Należy pamiętać, że na przykład w
int foo[] = { 1, 2, 3 }
tej{ 1, 2, 3 }
części znajduje się lista zamkniętych klamra z inicjalizatorów, z których każdy ma temperaturę sekwencji po niej.Inicjalizacja jest wykonywana w kolejności na liście inicjatorów.
Jednak wyrażenia inicjatora niekoniecznie są oceniane w kolejności.
Jednak to wciąż pozostawia kilka pytań bez odpowiedzi:
Czy punkty sekwencji są w ogóle istotne? Podstawowa zasada to:
a[2] = 1
jest wyrażeniem, ale inicjalizacja nie.Jest to nieco sprzeczne z załącznikiem J:
Załącznik J mówi, że liczą się wszelkie modyfikacje, a nie tylko modyfikacje wyrażeniami. Biorąc jednak pod uwagę, że załączniki są nienormatywne, prawdopodobnie możemy to zignorować.
W jaki sposób inicjalizacje podobiektów są sekwencjonowane w odniesieniu do wyrażeń inicjatora? Czy wszystkie inicjatory są oceniane jako pierwsze (w jakiejś kolejności), a następnie podobiekty są inicjowane z wynikami (w kolejności na liście inicjatorów)? Czy można je przeplatać?
Myślę, że
int a[5] = { a[2] = 1 }
jest wykonywany w następujący sposób:a
jest przydzielana po wprowadzeniu zawierającego ją bloku. W tym momencie zawartość jest nieokreślona.a[2] = 1
), po którym następuje punkt sekwencji. Zapisuje1
wa[2]
i powroty1
.1
służy do zainicjowaniaa[0]
(pierwszy inicjator inicjuje pierwszy podobiekt).Ale tu robi się rozmyty, ponieważ pozostałe elementy (
a[1]
,a[2]
,a[3]
,a[4]
) mają być inicjowane0
, ale nie jest jasne, gdy: zdarza się, zanima[2] = 1
jest oceniany? Jeśli tak,a[2] = 1
czy "wygra" i nadpiszea[2]
, ale czy to przypisanie będzie miało niezdefiniowane zachowanie, ponieważ nie ma punktu sekwencji między zerową inicjalizacją a wyrażeniem przypisania? Czy punkty sekwencji są w ogóle istotne (patrz wyżej)? A może zerowa inicjalizacja ma miejsce po ocenie wszystkich inicjatorów? Jeśli tak,a[2]
powinno to skończyć0
.Ponieważ standard C nie definiuje jasno, co się tutaj dzieje, uważam, że zachowanie jest nieokreślone (przez pominięcie).
źródło
a[0]
podobiektu przed oceną jego inicjalizatora, a ocena dowolnego inicjalizatora obejmuje punkt sekwencji (ponieważ jest to „pełne wyrażenie”). Dlatego uważam, że modyfikowanie inicjowanego podobiektu jest uczciwą grą.Przypuszczalnie
a[2]=1
inicjalizuje się jakoa[2]
pierwsza, a wynik wyrażenia jest używany do inicjalizacjia[0]
.Od N2176 (projekt C17):
Wydawałoby się więc, że produkcja
1 0 0 0 0
również byłaby możliwa.Wniosek: nie pisz inicjatorów, które modyfikują zainicjowaną zmienną w locie.
źródło
{...}
ekspresji, który inicjujea[2]
się0
ia[2]=1
sub-ekspresji, który inicjujea[2]
się1
.{...}
jest listą inicjalizacyjną ze wzmocnieniem. To nie jest wyrażenie.Myślę, że standard C11 obejmuje to zachowanie i mówi, że wynik jest nieokreślony i nie sądzę, aby C18 wprowadził jakiekolwiek istotne zmiany w tym obszarze.
Język standardowy nie jest łatwy do przeanalizowania. Odpowiednią sekcją normy jest §6.7.9 Inicjalizacja . Składnia jest udokumentowana jako:
Zwróć uwagę, że jednym z terminów jest wyrażenie przypisania , a ponieważ
a[2] = 1
jest to niewątpliwie wyrażenie przypisania, jest dozwolone wewnątrz inicjatorów dla tablic o niestatycznym czasie trwania:Jednym z kluczowych akapitów jest:
Kolejny kluczowy akapit to:
Jestem prawie pewien, że paragraf 23 wskazuje, że zapis w pytaniu:
prowadzi do nieokreślonego zachowania. Przypisanie do
a[2]
jest efektem ubocznym, a kolejność oceny wyrażeń jest nieokreślona względem siebie. W związku z tym nie sądzę, aby można było odwołać się do standardu i twierdzić, że konkretny kompilator obsługuje to poprawnie lub nieprawidłowo.źródło
My Understanding
a[2]=1
zwraca wartość 1 więc kod staje sięint a[5]={1}
przypisz wartość dla [0] = 1Stąd wypisuje 1 dla [0]
Na przykład
źródło
Na zagadkę staram się udzielić krótkiej i prostej odpowiedzi:
int a[5] = { a[2] = 1 };
a[2] = 1
jest ustawiony. Oznacza to, że tablica mówi:0 0 1 0 0
{ }
nawiasach, które są używane do inicjalizacji tablicy w kolejności, przyjmuje pierwszą wartość (czyli1
) i ustawia ją naa[0]
. To tak, jakby zostałoint a[5] = { a[2] };
tam, gdzie już dotarliśmya[2] = 1
. Wynikowa tablica to teraz:1 0 1 0 0
Inny przykład:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Mimo że kolejność jest nieco arbitralna, zakładając, że jest przesuwana od lewej do prawej, będzie przebiegać w tych 6 krokach:źródło
A = B = C = 5
nie jest deklaracją (ani inicjalizacją). Jest to normalne wyrażenie, które analizuje się jako,A = (B = (C = 5))
ponieważ=
operator jest prawoskrętny. To naprawdę nie pomaga w wyjaśnieniu, jak działa inicjalizacja. Tablica faktycznie zaczyna istnieć w momencie wprowadzenia bloku, w którym została zdefiniowana, co może nastąpić na długo przed wykonaniem właściwej definicji.a[2] = 1
wyrażenia inicjalizującego? Zaobserwowany wynik jest taki, jakby był, ale norma nie wydaje się określać, że tak powinno być. To jest centrum kontrowersji, a ta odpowiedź całkowicie go pomija.Przypisanie
a[2]= 1
jest wyrażeniem, które ma wartość1
i zasadniczo zostało napisaneint a[5]= { 1 };
(za[2]
przypisanym efektem ubocznym1
).źródło
Myślę, że
int a[5]={ a[2]=1 };
to dobry przykład dla programisty strzelającego sobie w stopę.Mógłbym ulec pokusie, by pomyśleć, że chodziło Ci o to,
int a[5]={ [2]=1 };
że będzie to inicjator wyznaczony przez C99 ustawiający element 2 na 1, a resztę na zero.W rzadkich przypadkach, o których naprawdę miałeś na myśli
int a[5]={ 1 }; a[2]=1;
, to byłby zabawny sposób pisania. W każdym razie do tego sprowadza się twój kod, mimo że niektórzy tutaj zauważyli, że nie jest dobrze zdefiniowany, kiedy zapisa[2]
jest faktycznie wykonywany. Pułapka polega na tym, żea[2]=1
nie jest to wyznaczony inicjator, ale proste przypisanie, które samo w sobie ma wartość 1.źródło