Jaki jest cel używania związku z tylko jednym członkiem?

89

Kiedy czytałem kod źródłowy seastar , zauważyłem, że istnieje zwana struktura związkowa, tx_sidektóra ma tylko jednego członka. Czy to jakiś hack, aby poradzić sobie z pewnym problemem?

Do Twojej wiadomości wklejam tx_sideponiższą strukturę:

union tx_side {
    tx_side() {}
    ~tx_side() {}
    void init() { new (&a) aa; }
    struct aa {
        std::deque<work_item*> pending_fifo;
    } a;
} _tx;
daoliker
źródło
1
Potencjalny duplikat stackoverflow.com/questions/26572432/... .
Max Langhof
5
@MaxLanghof W tym pytaniu i odpowiadających odpowiedziach nie wspomniano o celu zastosowania takiej struktury związkowej.
daoliker
Czy masz przykład wykorzystania tego członka?
n314159,
4
Właśnie dlatego tak naprawdę nie wykorzystałem mojego wiążącego ścisłego głosowania. Ale nie jestem pewien, czego dokładnie oczekujesz od odpowiedzi na twoje pytanie, które nie wynikają bezpośrednio z odpowiedzi tam. Przypuszczalnie celem użycia unionzamiast structjednej jest jedna lub więcej różnic między nimi. Jest to dość niejasna technika, więc jeśli nie pojawi się oryginalny autor tego kodu, nie jestem pewien, czy ktoś może udzielić wiarygodnej odpowiedzi na pytanie, jaki problem ma rozwiązać (jeśli w ogóle).
Max Langhof
2
Moje przypuszczenie jest takie, że unia służy albo do opóźniania budowy (co w tym przypadku jest nieco bezcelowe), albo do zapobiegania zniszczeniu (co prowadzi do wycieku pamięci) pending_fifo. Ale trudno powiedzieć bez przykładu użycia.
Konstantin Stupnik

Odpowiedzi:

82

Ponieważ tx_sideto związek, tx_side()nie inicjuje / nie konstruuje automatycznie ai ~tx_side()nie niszczy go automatycznie. Umożliwia to drobiazgową kontrolę nad czasem życia, aa także pending_fifopoprzez nowe połączenia i ręczne wywołania destruktora (biednego człowieka std::optional).

Oto przykład:

#include <iostream>

struct A
{
    A() {std::cout << "A()\n";}
    ~A() {std::cout << "~A()\n";}
};

union B
{
    A a;
    B() {}
    ~B() {}
};

int main()
{
    B b;
}

Tutaj B b;nic nie drukuje, ponieważ anie jest konstruowane ani niszczone.

Gdyby Bbył struct, B()zadzwoniłby A()i~B() dzwoniłby ~A(), a ty nie byłbyś w stanie temu zapobiec.

HolyBlackCat
źródło
23
@daoliker niekoniecznie jest przypadkowy, ale nieprzewidywalny. Taki sam jak każda inna niezainicjowana zmienna. Nie możesz założyć, że jest losowy; z tego co wiesz, może zawierać hasło użytkownika, o które wcześniej prosiłeś.
user253751,
5
@daoliker: Poprzedni komentarz jest zbyt optymistyczny. Losowe bajty miałyby wartości z zakresu 0–255, ale jeśli wczytasz niezainicjowany bajt int, możesz go otrzymać 0xCCCCCCCC. Odczytywanie niezainicjowanych danych jest zachowaniem niezdefiniowanym, a to, co może się zdarzyć, polega na tym, że kompilator po prostu odrzuca próbę. To nie tylko teoria. Debian popełnił właśnie ten błąd i złamał ich implementację OpenSSL. Mieli kilka prawdziwych losowych bajtów, dodali niezainicjowaną zmienną, a kompilator powiedział: „wynik jest niezdefiniowany, więc równie dobrze może wynosić zero”. Zero oczywiście nie jest już przypadkowe.
MSalters
1
@MSalters: Czy masz źródło tego roszczenia? Ponieważ to, co mogę znaleźć, sugeruje, że nie tak się stało: to nie kompilator go usunął, ale programiści. Szczerze mówiąc, zdziwiłbym się, gdyby którykolwiek autor kompilatorów podjął tak niewiarygodnie złą decyzję. (patrz stackoverflow.com/questions/45395435/… )
Jack Aidley
5
@JackAidley: Jakie dokładne roszczenie? Masz dobry link, wygląda na to, że opowieść została odwrócona. OpenSSL pomylił logikę i użył niezainicjowanej zmiennej w taki sposób, że kompilator mógł legalnie przyjąć dowolny wynik. Debian poprawnie to zauważył, ale złamał poprawkę. Jeśli chodzi o „kompilatory podejmujące takie złe decyzje”; nie podejmują tej decyzji. Niezdefiniowane zachowanie to zła decyzja. Optymalizatory są zaprojektowane do działania na poprawnym kodzie. Na przykład GCC aktywnie zakłada brak podpisanego przepełnienia. Zakładanie, że „brak niezainicjowanych danych” jest równie uzasadnione; można go wykorzystać do wyeliminowania niemożliwych ścieżek kodu.
MSalters
1
@JackAidley Napotkałem podobne problemy, co @MSalters wspomina w moim własnym kodzie; Błędnie założyłem, że niezainicjowana zmienna będzie pusta, i zaskoczyło mnie, gdy kolejne != 0porównanie się sprawdziło. Od tego czasu dodałem flagi kompilatora do traktowania niezainicjowanych zmiennych jako błędów, aby upewnić się, że nie wpadnę ponownie w tę pułapkę.
Tom Lint
0

Krótko mówiąc, jeśli jednoznacznie nie przypisano / nie zainicjowano wartości, jedno połączenie nie inicjuje przydzielonej pamięci. Funkcjonalność tę można osiągnąć za pomocą std:: optionalc ++ 17.

Witryny
źródło
2
To myląca odpowiedź. Unia z tylko jednym członkiem będzie miała taki sam rozmiar jak członek. Ta pamięć po prostu nie zostanie zainicjowana, dopóki członek nie zostanie zainicjowany.
Kirill Dmitrenko,