Ważne jest, aby zdać sobie sprawę, że kod, który tworzy kompilator, nie ma rzeczywistej wiedzy o twoich strukturach danych (ponieważ coś takiego nie istnieje na poziomie zespołu), podobnie jak optymalizator. Kompilator tworzy tylko kod dla każdej funkcji , a nie struktury danych .
Ok, zapisuje też stałe sekcje danych i takie.
Na tej podstawie możemy już powiedzieć, że optymalizator nie „usunie” ani „wyeliminuje” elementów, ponieważ nie wyprowadza struktur danych. Wyprowadza kod , który może lub nie może korzystać z elementów członkowskich, a jednym z jego celów jest oszczędzanie pamięci lub cykli poprzez eliminację bezcelowych zastosowań (tj. Zapisywanie / odczytywanie) elementów członkowskich.
Istota tego polega na tym, że „jeśli kompilator może udowodnić w zakresie funkcji (w tym funkcji, które zostały do niej wstawione), że nieużywany element członkowski nie ma znaczenia dla sposobu działania funkcji (i tego, co zwraca), to są duże szanse, że obecność członka nie powoduje kosztów ogólnych ”.
Gdy sprawisz, że interakcje funkcji ze światem zewnętrznym będą bardziej skomplikowane / niejasne dla kompilatora (pobierz / zwróć bardziej złożone struktury danych, np. A std::vector<Foo>
, ukryj definicję funkcji w innej jednostce kompilacji, zabroń / zniechęcaj do wstawiania itp.) , staje się coraz bardziej prawdopodobne, że kompilator nie może udowodnić, że nieużywany element członkowski nie ma żadnego efektu.
Nie ma tutaj sztywnych reguł, ponieważ wszystko zależy od optymalizacji dokonanych przez kompilator, ale dopóki robisz trywialne rzeczy (takie jak pokazane w odpowiedzi YSC), jest bardzo prawdopodobne, że nie będzie żadnego narzutu, podczas gdy robienie skomplikowanych rzeczy (np. a std::vector<Foo>
z funkcji zbyt dużej do umieszczenia w tekście) prawdopodobnie spowoduje koszty ogólne.
Aby zilustrować ten punkt, rozważ następujący przykład :
struct Foo {
int var1 = 3;
int var2 = 4;
int var3 = 5;
};
int test()
{
Foo foo;
std::array<char, sizeof(Foo)> arr;
std::memcpy(&arr, &foo, sizeof(Foo));
return arr[0] + arr[4];
}
Robimy tutaj nietrywialne rzeczy (pobieramy adresy, sprawdzamy i dodajemy bajty z reprezentacji bajtów ), a mimo to optymalizator może dowiedzieć się, że wynik jest zawsze taki sam na tej platformie:
test(): # @test()
mov eax, 7
ret
Członkowie nie tylko Foo
nie zajmowali pamięci, a Foo
nawet nie zaistnieli! Jeśli istnieją inne zastosowania, których nie można zoptymalizować, np. sizeof(Foo)
Może to mieć znaczenie - ale tylko dla tego segmentu kodu! Gdyby wszystkie zastosowania można było zoptymalizować w ten sposób, wówczas istnienie np. var3
Nie wpływa na generowany kod. Ale nawet jeśli zostanie użyty w innym miejscu, test()
pozostanie zoptymalizowany!
W skrócie: każde użycie Foo
jest optymalizowane niezależnie. Niektórzy mogą zużywać więcej pamięci z powodu niepotrzebnego członka, inni mogą nie. Więcej informacji znajdziesz w instrukcji kompilatora.