Czy struktura jest deterministyczna?

Na przykład, powiedzmy, że mam dwie równoważne struktury a i b w różnych projektach:

typedef struct _a
{
    int a;
    double b;
    char c;
} a;

typedef struct _b
{
    int d;
    double e;
    char f;
} b;

Zakładając, że nie użyłem żadnych dyrektyw jak {[3] } i te struktury są kompilowane na tym samym kompilatorze na tej samej architekturze, czy będą miały identyczne wypełnienie między zmiennymi?

 42
Author: Govind Parmar, 2017-06-11

8 answers

Kompilator jest deterministyczny; gdyby nie był, oddzielna kompilacja byłaby niemożliwa. Dwie różne jednostki tłumaczeniowe z tą samą deklaracją struct będą ze sobą współpracować; jest to gwarantowane przez §6.2.7/1: typy kompatybilne i typy kompozytowe.

Ponadto dwa różne Kompilatory na tej samej platformie powinny współdziałać, chociaż nie jest to gwarantowane przez standard. (To kwestia jakości realizacji.), Aby umożliwić współdziałanie, twórcy kompilatorów uzgodnić platformę ABI (Application Binary Interface), która będzie zawierać dokładną specyfikację sposobu reprezentacji typów kompozytowych. W ten sposób program skompilowany z jednym kompilatorem może używać modułów bibliotecznych skompilowanych z innym kompilatorem.

Ale nie interesuje Cię tylko determinizm; chcesz również, aby układ dwóch różnych typów był taki sam.

Zgodnie ze standardem, dwa typy struct są kompatybilne, jeśli ich członkowie (w kolejności) są kompatybilne i jeśli ich tagi i nazwy członków są takie same. Ponieważ twój przykład structs ma różne znaczniki i nazwy, nie są one kompatybilne, nawet jeśli ich typy członkowskie są, więc nie możesz użyć jednego tam, gdzie drugi jest wymagany.

Może się wydawać dziwne, że standard pozwala znacznikom i nazwom członków wpływać na zgodność. Standard wymaga, aby członkowie struktury byli ułożeni w kolejności deklaracji, więc nazwy nie mogą zmieniać kolejności członków w strukturze. Dlaczego więc mogli wpływ padding? Nie znam żadnego kompilatora, ale elastyczność standardu opiera się na zasadzie, że wymagania powinny być minimum niezbędnym do zagwarantowania poprawnego wykonania. Aliasowanie inaczej oznaczonych struktur nie jest dozwolone w jednostce tłumaczeń, więc nie ma potrzeby akceptowania ich pomiędzy różnymi jednostkami tłumaczeń. A więc standard na to nie pozwala. (Byłoby uzasadnione, aby implementacja wstawiała informacje o typie w struct's padding bajtów, nawet jeśli konieczne było deterministyczne dodanie wypełnienia, aby zapewnić miejsce na takie informacje. Jedynym ograniczeniem jest to, że padding nie może być umieszczony przed pierwszym członem struct.)

Platforma ABI może określać układ typu kompozytowego bez odwoływania się do jego tagów lub nazw członków. Na konkretnej platformie, z platformą ABI, która ma taką specyfikację i kompilator udokumentowany, aby był zgodny z platformą ABI, można uciec od aliasingu, chociaż to nie byłoby poprawne technicznie i oczywiście warunki wstępne sprawiają, że nie jest przenośny.

 55
Author: rici,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 20:21:51

Sam standard C nic o tym nie mówi, więc zgodnie z zasadą nie możesz być pewien.

Ale: najprawdopodobniej twój kompilator przylega do jakiegoś konkretnego ABI, w przeciwnym razie komunikacja z innymi bibliotekami i systemem operacyjnym byłaby koszmarem. W tym ostatnim przypadku ABI zazwyczaj przepisuje Dokładnie Jak działa pakowanie.

Na przykład:

  • Na x86_64 Linux / BSD, SystemV AMD64 ABI jest Referencja. Tutaj (§3.1) Dla każdego prymitywnego typu danych procesora jest wyszczególniona korespondencja z typem C, jego rozmiar i wymagania dotyczące wyrównania, i jest wyjaśnione, jak używać tych danych do tworzenia układu pamięci bitfields, structs i unions; Wszystko (poza rzeczywistą zawartością wyściółki) jest określona i deterministyczna. To samo dotyczy wielu innych architektur, zobacz te linki .

  • Ramię zaleca swój EABI dla swoich procesorów, a za nim zwykle podążają zarówno Linux, jak i Windows; wyrównanie agregatów jest określone w" Procedure Call Standard for the arm Architecture Documentation", §4.3.

  • W Windows nie ma standardu cross-vendor, ale VC++ zasadniczo dyktuje ABI, do którego stosuje się praktycznie każdy kompilator; można go znaleźć tutaj {[16] } dla x86_64, tutaj dla ARM (ale dla części zainteresowania tego pytania odnosi się tylko do ramienia EABI).

 15
Author: Matteo Italia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 16:20:36

Każdy zdrowy kompilator stworzy identyczny układ pamięci dla obu struktur. Kompilatory są zwykle pisane jako doskonale deterministyczne programy. Niedeterminizm musiałby być dodany wyraźnie i celowo, a ja nie dostrzegam korzyści z takiego działania.

Jednakże, to nie pozwala Ci rzucić struct _a* do struct _b* i uzyskać dostęp do jego danych za pośrednictwem obu. Afaik, nadal byłoby to naruszenie ścisłych zasad aliasingu, nawet jeśli układ pamięci jest identyczny, jak pozwala kompilatorowi na zmianę kolejności dostępu za pomocą struct _a* z dostępem za pomocą struct _b* , co skutkowałoby nieprzewidywalnym, niezdefiniowanym zachowaniem.

 10
Author: cmaster - reinstate monica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 15:36:55

Czy będą miały identyczne wypełnienia między zmiennymi?

W praktyce najczęściej lubią mieć ten sam układ pamięci.

W teorii, ponieważ standard nie mówi wiele o tym, jak należy stosować wyściółkę na obiektach, nie można tak naprawdę założyć niczego na wyściółce między elementami.

Poza tym, nie rozumiem nawet, dlaczego chcesz wiedzieć / zakładać coś o wypełnieniach między członkami struktury. wystarczy napisać standardowy, zgodny kod C i nic Ci nie będzie.

 8
Author: David Haim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 15:04:08

Nie można podejść deterministycznie do układu struktury lub Unii w języku C na różnych systemach.

Podczas gdy wiele razy mogłoby się wydawać, że układ generowany przez różne Kompilatory jest taki sam, należy wziąć pod uwagę zbieżność podyktowaną praktyczną i funkcjonalną wygodą projektowania kompilatorów w zakresie swobody wyboru pozostawionej programiście przez standard, a tym samym nieskuteczną.

Norma C11 ISO / IEC 9899: 2011 , prawie bez zmian na podstawie poprzednich norm, wyraźnie określonych w pkt 6.7.2.1 struktura i specyfikacja Unii:

Każdy Nie-bitowy element pola struktury lub obiektu Unii jest wyrównywany w sposób określony w implementacji, odpowiedni do jego typu.

Nawet najgorszy przypadek bitfieldów, gdzie duża Autonomia pozostawiona jest programiście:

Implementacja może przydzielić dowolną adresowalną jednostkę pamięci na tyle dużą, aby pomieścić pole bitowe. Jeśli pozostanie wystarczająco dużo miejsca, a pole bitowe, które następuje bezpośrednio po innym polu bitowym w strukturze, jest pakowane do sąsiednich bitów tej samej jednostki. W przypadku braku wystarczającej ilości miejsca, to, czy pole bitowe, które nie pasuje, jest umieszczane w następnej jednostce, czy też nakłada się na sąsiednie jednostki, jest definiowane jako implementacja. Kolejność alokacji pól bitowych w jednostce (od wysokiego do niskiego lub od niskiego do wysokiego) jest zdefiniowana jako implementacja. Wyrównanie adresowalnej jednostki pamięci nie jest określone.

Po prostu policz jak wielokrotnie w tekście pojawiają się terminy "implementation-defined" i "unexpectified".

Zgodziłem się, że aby sprawdzić wersję kompilatora, maszynę i architekturę docelową przed każdym uruchomieniem, aby użyć struktury lub Unii Wygenerowanej na innym systemie, powinieneś mieć przyzwoitą odpowiedź na swoje pytanie.

Teraz powiedzmy, że tak, jest sposób na obejście.

Być jasne, że nie jest to zdecydowanie rozwiązanie, ale jest wspólne podejście, które można znaleźć wokół gdy wymiana struktur danych jest współdzielona między różnymi systemami: elementy struktury pack na wartości 1 (standardowy rozmiar znaku).

Zastosowanie opakowania i dokładna definicja struktury może prowadzić do wystarczająco wiarygodnej deklaracji, która może być stosowana w różnych systemach. Pakowanie zmusza kompilator do usunięcia implementacji zdefiniowanych wyrównań, zmniejszając ewentualne niezgodności ze standardem. Ponadto unikając używania bitfieldów można usunąć pozostałości zależne od implementacji niespójności. Wreszcie, wydajność dostępu, spowodowana brakiem wyrównania, może być odtworzona poprzez ręczne dodanie atrapy deklaracji pomiędzy elementami, wytworzonej w taki sposób, aby wymusić cofnięcie każdego pola przy prawidłowym wyrównaniu.

Jako ostatni przypadek musisz wziąć pod uwagę wypełnienie na końcu struktury, które niektóre Kompilatory dodają, ale ponieważ nie ma użytecznych danych, możesz je zignorować(chyba że dla dynamicznego przydzielania przestrzeni, ale znowu możesz sobie z tym poradzić).

 5
Author: Frankie_C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-06 13:32:34

ISO C mówi, że dwa typy struct w różnych jednostkach tłumaczeniowych są kompatybilne, jeśli mają ten sam znacznik i pręty. Dokładniej, oto dokładny tekst ze standardu C99:

6.2.7 Typ kompatybilny i typ złożony

Dwa typy mają typ zgodny, jeśli ich typy są takie same. Dodatkowe zasady ustalania, czy dwa typy są kompatybilne, opisano w 6.7.2 Dla specyfikatorów typów, w 6.7.3 dla kwalifikatorów typów i w 6.7.5 dla deklaratorów. Ponadto dwa typy struktury, Unii lub wymienione zadeklarowane w oddzielnych jednostkach tłumaczeniowych są kompatybilne, jeśli ich znaczniki i elementy spełniają następujące wymagania: jeśli jeden jest zadeklarowany za pomocą znacznika, drugi jest zadeklarowany za pomocą tego samego znacznika. Jeśli oba są typami zupełnymi, to następujące zastosowanie mają dodatkowe wymagania: istnieje korespondencja jeden do jednego między ich członkami, taka, że każda para odpowiadających im członków jest zadeklarowana z Kompatybilnymi typami i taka, że jeśli jeden członek odpowiedniej pary jest zadeklarowany z nazwą, drugi członek jest zadeklarowany z tą samą nazwą. W przypadku dwóch struktur odpowiedni członkowie są deklarowani w tej samej kolejności. Dla dwóch struktur lub związków odpowiednie pola bitowe muszą mieć te same szerokości. Dla dwóch wyliczeń odpowiadające im elementy mają te same wartości.

Wydaje się to bardzo dziwne, jeśli interpretujemy to z punktu widzenia, " co, nazwy tagów lub członków mogą mieć wpływ na padding?"Ale w zasadzie reguły są po prostu tak surowe, jak to tylko możliwe, pozwalając na wspólny przypadek: wiele jednostek tłumaczeniowych współdzielących dokładny tekst deklaracji struct za pośrednictwem pliku nagłówkowego. Jeśli programy przestrzegają luźniejszych zasad, nie są w błędzie; po prostu nie polegają na wymaganiach dotyczących zachowania ze standardu, ale z innego miejsca.

W twoim przykładzie, używasz reguł języka, mając tylko strukturalną równoważność, ale nie równoważne nazwy znaczników i członków. W w praktyce nie jest to w rzeczywistości wymuszone; typy struktur z różnymi znacznikami i nazwami członków w różnych jednostkach translacyjnych są de facto fizycznie kompatybilne. Od tego zależą wszelkiego rodzaju technologie, takie jak powiązania z języków innych niż C do bibliotek C.

Jeśli oba projekty są w C (lub c++), prawdopodobnie warto byłoby spróbować umieścić definicję we wspólnym nagłówku.

Dobrym pomysłem jest również obrona przed problemami z wersjonowaniem, takie jak Pole rozmiaru:

// Widely shared definition between projects affecting interop!
// Do not change any of the members.
// Add new ones only at the end!
typedef struct a
{
    size_t size; // of whole structure
    int a;
    double b;
    char c;
} a;

Chodzi o to, że ktokolwiek zbuduje instancję a, musi zainicjować pole size na sizeof (a). Następnie, gdy obiekt zostanie przekazany do innego komponentu oprogramowania (być może z innego projektu), może sprawdzić jego rozmiar w stosunku do sizeof (a). Jeśli pole size jest mniejsze, to wie, że oprogramowanie, które zbudowało a używa starej deklaracji z mniejszą liczbą członków. W związku z tym nie można uzyskać dostępu do nieistniejących członków.

 4
Author: Kaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 23:23:59

Każdy konkretny kompilator powinien być deterministyczny, ale pomiędzy dowolnymi dwoma Kompilatory, a nawet ten sam kompilator z różnymi opcjami kompilacji, lub nawet pomiędzy różnymi wersjami tego samego kompilatora, wszystkie zakłady są wyłączone.

O wiele lepiej, jeśli nie zależy ci na szczegółach struktura, lub jeśli tak, należy osadzić kod, aby sprawdzić w czasie wykonywania że struktura jest rzeczywiście tak, jak zależy.

Dobrym tego przykładem jest niedawna zmiana z 32 NA 64 bit architektur, gdzie nawet jeśli nie zmienisz rozmiaru liczb całkowitych używane w strukturze, domyślne pakowanie częściowych liczb całkowitych zmieniło; gdzie wcześniej 3 32bitowe liczby całkowite z rzędu pakowałyby się idealnie, teraz pakują się w dwa gniazda 64-bitowe.

Nie można przewidzieć, jakie zmiany mogą wystąpić w przyszłości; jeśli zależy ci na szczegółach, które nie są gwarantowane przez język, takie jako pakowanie struktury, powinieneś zweryfikować swoje założenia w czasie wykonywania.

 2
Author: ddyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 18:58:06

Tak. Zawsze powinieneś przyjąć deterministyczne zachowanie ze swojego kompilatora.

[edytuj] z poniższych komentarzy wynika, że wielu programistów Javy czyta powyższe pytanie. Bądźmy szczerzy: struktury C nie generują żadnych nazw, skrótów ani podobieństw w plikach obiektowych, bibliotekach czy bibliotekach DLL. Sygnatury funkcji C również się do nich nie odnoszą. Co oznacza, że nazwy członków mogą być zmieniane na kaprys-naprawdę! - pod warunkiem, że typ i kolejność zmiennych składowych jest taka sama. W C dwie struktury w przykładzie są równoważne, ponieważ opakowanie nie zmienia się. co oznacza, że poniższe nadużycie jest całkowicie poprawne w C, a na pewno jest znacznie gorsze nadużycie, które można znaleźć w niektórych z najczęściej używanych bibliotek.

[EDIT2] nikt nie powinien śmiać się wykonać żadnej z następujących czynności w C++

/* the 3 structures below are 100% binary compatible */
typedef struct _a { int a; double b; char c; }
typedef struct _b { int d; double e; char f; }
typedef struct SOME_STRUCT { int my_i; double my_f; char my_c[1]; }

struct _a a = { 1, 2.5, 'z' };
struct _b b;

/* the following is valid, copy b -> a  */
*(SOME_STRUCT*)&a = *(SOME_STRUCT*)b;
assert((SOME_STRUCT*)&a)->my_c[0] == b.f);
assert(a.c == b.f);

/* more generally these identities are always true. */
assert(sizeof(a) == sizeof(b));
assert(memcmp(&a, &b, sizeof(a)) == 0);
assert(pure_function_requiring_a(&a) == pure_function_requiring_a((_a*)&b));
assert(pure_function_requiring_b((b*)&a) == pure_function_requiring_b(&b));

function_requiring_a_SOME_STRUCT_pointer(&a);  /* may generate a warning, but not all compiler will */
/* etc... the name space abuse is limited to the programmer's imagination */
 -1
Author: Michaël Roy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-12 23:48:22