Różne wartości pola podczas wyświetlania tablicy struktur

0

Dlaczego w poniższym programie wszystkie wartości wyrażenia (*arr[n]).i dla "second printing" są takie same, a dla "first printing" różne? Gdzie leży różnica między wyświetlaniem w jednej pętli, a wyświetlaniem w drugiej? Patrzę i patrzę w ten kod, i nic. Indeksy są takie same w obu pętlach dla odpowiadających sobie elementów, wskaźniki też się takie samej wydają (o ile nie pomyliłem się podczas ich wzrokowego porównywania).

#include <stdio.h>

int main(void) {
	struct s { int i; } s;
	struct s *arr[5];
	for (int n = 0; n < 5; ++n) {
		arr[n] = &(struct s){ .i = n };
		printf("First printing: %d, %p, i == %d\n", n, &arr[n], (*arr[n]).i);
	}
	for (int n = 0; n < 5; ++n) {
		printf("Second printing: %d, %p, i == %d\n", n, &arr[n], (*arr[n]).i);
	}
	return 0;
}

UPDATE Wersja live tego programu: https://ideone.com/6t3VR1

6

Zamień struct s { int i; } s;
na
struct s_t { int i; } s;
to zobaczysz dlaczego.

W skrócie:

  1. używasz wskaźników do struktur stosowych - da się przeżyć
  2. ale każdy wskaźnik wskazuje na tę samą strukturę - błąd
  3. ponieważ przy tym nazewnictwie nie wiesz co jest zmienną a co typem.

Moja sugestia: zamień tablicę na nie-wskaźnikową lub alokuj struktury przed użyciem (malloc/calloc).
https://www.delftstack.com/howto/c/c-array-of-structs/

3

Skompiluj z -fsanitize=address to zobaczysz co się dzieje. @vpiotr pomijasz imho najważniejszą przyczynę błędu: arr[n] = &(struct s){ .i = n }; @Silv to co zrobiłeś tutaj to:
a. stworzenie zmiennej automatycznej typu struct s w postaci tzw. compound literal (struct s){ .i = n } (OK)
b. wzięcie jej adresu i wpisanie do arr[n], tj. arr[n] = &(... (nadal OK)
c. zmienna jest niszczona, ale jej adres zostaje w tablicy no i jest problem, tzn. UB ;)

0
vpiotr napisał(a):

Zamień struct s { int i; } s;

na
struct s_t { int i; } s;
to zobaczysz dlaczego.

Zmieniłem; otrzymuję m.in. taki komunikat:

error: ‘struct s’ has no member named ‘i’

To naprowadza mnie na pomysł, że definicja struktury s (końcowe s) nie jest do niczego w tym przykładowym programie potrzebna. Nie wiem, czemu ją tam umieściłem. :) <zastanawia się>

Nadal jednak nie widzę rozwiązania problemu.

  1. używasz wskaźników do struktur stosowych - da się przeżyć

Wydaje mi się, że nie ma to związku z działaniem programu (bo "da się przeżyć" ;) ); ale z drugiej strony wydaje mi się, że trafna uwaga. Pewnie będę jeszcze w przyszłości o tym czytać.

  1. ale każdy wskaźnik wskazuje na tę samą strukturę - błąd
  2. ponieważ przy tym nazewnictwie nie wiesz co jest zmienną a co typem.

Tak, to prawda, odniosłem się do tego wyżej. Ale nie rozumiem: tę samą strukturę?

Moja sugestia: zamień tablicę na nie-wskaźnikową lub alokuj struktury przed użyciem (malloc/calloc).

Co do tablicy struktur – wiem, że działa tak, jak oczekuję; odpowiadające wartości wypisywane w obu pętlach są takie same. Chciałbym właśnie zobaczyć, czy można – a jeśli tak, to jak – uzyskać to samo działanie dla tablicy wskaźników na struktury.

Jeśli chodzi o korzystanie z malloc: pewnie będę jeszcze w przyszłości próbować. Póki co chciałbym uczynić powyższy program działającym za pomocą minimalnych zmian.

alagner napisał(a):

Skompiluj z -fsanitize=address to zobaczysz co się dzieje.

Patrzę właśnie na stronę podrzuconą przez @MarekR22 , ale niewiele rozumiem. ;) Może kiedyś zrozumiem.

@vpiotr pomijasz imho najważniejszą przyczynę błędu: arr[n] = &(struct s){ .i = n }; @Silv to co zrobiłeś tutaj to:
a. stworzenie zmiennej automatycznej typu struct s w postaci tzw. compound literal (struct s){ .i = n } (OK)
b. wzięcie jej adresu i wpisanie do arr[n], tj. arr[n] = &(... (nadal OK)
c. zmienna jest niszczona, ale jej adres zostaje w tablicy no i jest problem, tzn. UB ;)

Mam w głowie, że undefined behavior oznacza "każdy błąd jest uzasadniony". ;) Tutaj wolałbym wiedzieć więcej niż to, że to, że jest źle, jest oczekiwane. Zmienna jest "automatyczna"… A więc byłoby, że jest związana z tzw. "automatic storage duration" (w przeciwieństwie do tzw. "allocated storage duration"; https://en.cppreference.com/w/c/language/storage_class_specifiers), czy tak? Muszę o tym poczytać, bo na razie nie wiem, jakie wnioski wyciągać. (Dlaczegóż każde źródło w internecie pisze w innych słowach to samo! (Zresztą nie wiem, czy to samo). Nie można by pisać w tych samych słowach tego samego?)

3
Silv napisał(a):

Patrzę właśnie na stronę podrzuconą przez @MarekR22 , ale niewiele rozumiem. ;) Może kiedyś zrozumiem.

Dałem to jako komentarz do postu, który to wyraźnie opisuje na czym polega problem (pierwotnie napisałem coś podobnego, ale skasowałem, bo alagner mnie ubiegł).

alagner napisał(a):

c. zmienna jest niszczona, ale jej adres zostaje w tablicy no i jest problem, tzn. UB ;)

narzędzie mówi dokładnie to samo:

=================================================================
==1==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffc85094250 at pc 0x00000040123a bp 0x7ffc85094210 sp 0x7ffc85094208
READ of size 4 at 0x7ffc85094250 thread T0
    #0 0x401239 in main /app/example.c:13
    #1 0x7f9d131a70b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #2 0x4012dd in _start (/app/output.s+0x4012dd)
3

Ale nie rozumiem: tę samą strukturę?

Nie tę samą, ale efektywnie umieszczoną w tym samym miejscu (choć to detal implementacyjny, teoretycznie jakiś kompilator może robić inaczej i być zgodny ze standardem).

arr[n] = &(struct s){ .i = n };

Tutaj tworzysz nową strukturę tymczasową za pomocą (struct s){ .i = n }, bierzesz jej adres, zapisujesz go w tablicy, po czym usuwasz tę tymczasową strukturę. Adres w tablicy pokazuje więc w pustkę i jego dereferencja to UB.

Implementacyjnie, w następnym obrocie pętli utworzysz nową strukturę, a że jesteś w małym kawałku kodu na stosie, to będzie ona w tym samym miejscu pamięci co poprzednia, a więc poprzedni wskaźnik będzie na nią wskazywał.

1
        printf("First printing: %d, %p, i == %d\n", n, &arr[n], (*arr[n]).i);

Ja jeszcze dodam że zamiast (*arr[n]).i można napisać arr[n]->i i znaczy to dokładnie to samo.

2

@MarekR22: @Silv: nie ma żadnej konwersji (miejsca w komentarzu mi braknie). Jest utworzenie obiektu bez nazwy o czasie trwania takim jak okalającego go klamry.

{
  foo(&(struct s){ .i = x });
}

jest tożsame

{
  struct s unnamed = {x};
  foo(&unnamed);
}

tyle, że w tej drugiej wersji możesz się do struktury odnieść po nazwie, w Twojej - nie.
Jej czas życia kończy się z końcem klamerek, w których się ona znajduje (chyba, że jest poza jakimkolwiek blokiem w ogóle, wtedy jest implicit static).

Ficzer nazywa się compound literals, w standardzie jest od C99 i jest w 3 i trochę mylący choćby z uwagi na totalnie nieintuicyjny lifetime ;)

0

@alagner: Czyli wywołanie foo(&x) w Twoim kodzie jest poprawne czy niepoprawne?

@vpiotr (oraz kto będzie wiedzieć):

Silv napisał(a):
vpiotr napisał(a):
  1. używasz wskaźników do struktur stosowych - da się przeżyć

Wydaje mi się, że nie ma to związku z działaniem programu (bo "da się przeżyć" ;) ); ale z drugiej strony wydaje mi się, że trafna uwaga. Pewnie będę jeszcze w przyszłości o tym czytać.

Zapytam od razu: dlaczego "da się przeżyć", a nie "opcja dobra jak każda inna"?


PS @alagner "Poprawne" = "dalej, już w funkcji foo, nie będzie błędu".

2

Tak, jest poprawne. Chodzi po prostu o to, żeby nie zwrócić adresu obiektu już zniszczonego (po wyskoczeniu z danej ramki stosu) bo jego dereferencja to UB vide Twój przypadek. Ale jeśli tego pilnujesz to nie ma problemu. I dopóki nie używasz longjmp/setjmp i customowych mechanizmów obsługi błędów bo wtedy robi się jazda ;)

EDIT no i funkcja nie może próbować tej pamięci zwolnić (i chyba realokować). Ale z przekazaniem wskaźnika do zmiennej automatycznej samym w sobie problemu nie ma. Kwestia co może i nie może się potem z nim stać.

3

@MarekR22:
Na Twoją prośbę - wymądrzam się: ;)

6.5.2.5 Compound literals
Constraints
The type name shall specify an object type or an array of unknown size, but not a variable length array type.
(...)
Semantics
4 A postfix expression that consists of a parenthesized type name followed by a brace- enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.84)
5 If the type name specifies an array of unknown size, the size is determined by the initializer list as specified in 6.7.8, and the type of the compound literal is that of the completed array type. Otherwise (when the type name specifies an object type), the type of the compound literal is that specified by the type name. In either case, the result is an lvalue.

Oraz przypis:

Note that this differs from a cast expression. For example, a cast specifies a conversion to scalar types or void only, and the result of a cast expression is not an lvalue.

0
alagner napisał(a):

(…)
Oraz przypis:

Note that this differs from a cast expression. For example, a cast specifies a conversion to scalar types or void only, and the result of a cast expression is not an lvalue.

Do czego odnosi się "this"?

1

Normalnie zapis (typ)expression jest rzutowaniem, tutaj jest literałem złożonym (compound literal)

1 użytkowników online, w tym zalogowanych: 0, gości: 1