Czy zwracanie struktury w C lub c++jest bezpieczne?

Rozumiem, że nie powinno się tego robić, ale wydaje mi się, że widziałem przykłady, które robią coś takiego (uwaga kod niekoniecznie jest poprawny składniowo, ale idea jest tam)

typedef struct{
    int a,b;
}mystruct;

I oto Funkcja

mystruct func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

Zrozumiałem, że zawsze powinniśmy zwracać wskaźnik do struktury malloc'eda, jeśli chcemy zrobić coś takiego, ale jestem pewien, że widziałem przykłady, które robią coś takiego. Czy to prawda? Osobiście zawsze zwracam wskazówkę do struktura malloc ' a lub po prostu wykonaj przejście przez odniesienie do funkcji i zmodyfikuj tam wartości. (Ponieważ rozumiem, że gdy zakres funkcji się skończy, jakikolwiek stos został użyty do alokacji struktury może zostać nadpisany).

Dodajmy drugą część do pytania: czy to różni się w zależności od kompilatora? Jeśli tak, to jakie jest zachowanie najnowszych wersji kompilatorów dla komputerów stacjonarnych: gcc, g++ i Visual Studio?

Myśli na ten temat?

Author: Jonathan Leffler, 2012-03-06

11 answers

To jest całkowicie bezpieczne i nie jest złe, aby to zrobić. Ponadto: nie różni się w zależności od kompilatora.

Zazwyczaj, gdy (jak w twoim przykładzie) twoja struktura nie jest zbyt duża, argumentowałbym, że takie podejście jest nawet lepsze niż zwracanie struktury malloc ' a (malloc jest kosztowną operacją).

 68
Author: Pablo Santa Cruz,
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
2013-05-31 08:16:06

Jest całkowicie bezpieczny.

Zwracasz według wartości. To, co doprowadziłoby do niezdefiniowanego zachowania, to powrót przez odniesienie.

//safe
mystruct func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

//undefined behavior
mystruct& func(int c, int d){
    mystruct retval;
    retval.a = c;
    retval.b = d;
    return retval;
}

Zachowanie Twojego fragmentu jest całkowicie poprawne i zdefiniowane. Nie różni się w zależności od kompilatora. Już dobrze!

Osobiście zawsze zwracam wskaźnik do struktury malloc ' a

Nie powinieneś. powinieneś unikać dynamicznie przydzielanej pamięci, jeśli to możliwe.

Lub po prostu przechodzić obok odniesienie do funkcji i zmiana wartości tam.

Ta opcja jest całkowicie poprawna. To kwestia wyboru. Ogólnie rzecz biorąc, robisz to, jeśli chcesz zwrócić coś innego z funkcji, podczas modyfikowania oryginalnej struktury.

Ponieważ rozumiem, że gdy zakres funkcji jest nad, niezależnie od tego, jaki stos został użyty do przydzielenia struktury, może być nadpisane

To jest złe. Miałem na myśli, że to w pewnym sensie poprawne, ale zwracasz Kopia struktury tworzonej wewnątrz funkcji. teoretycznie . W praktyce, RVO może i prawdopodobnie nastąpi. Zapoznaj się z optymalizacją wartości zwrotu. Oznacza to, że chociaż retval wydaje się wychodzić poza zakres po zakończeniu funkcji, może ona być faktycznie zbudowana w kontekście wywołania, aby zapobiec dodatkowej kopii. Jest to optymalizacja, którą kompilator może zaimplementować za darmo.
 64
Author: Luchian Grigore,
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
2012-03-06 19:59:58

Życie obiektu mystruct w twojej funkcji rzeczywiście kończy się, gdy opuścisz tę funkcję. Obiekt jest jednak przekazywany przez wartość w instrukcji return. Oznacza to, że obiekt jest kopiowany z funkcji do funkcji wywołującej. Oryginalny obiekt zniknął, ale Kopia żyje dalej.

 9
Author: Joseph Mansfield,
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
2012-03-06 19:57:30

Nie tylko bezpiecznie jest zwracać struct W C (lub class W C++, gdzie struct-S są w rzeczywistości class-es z domyślnymi członkami public:), ale robi to wiele programów.

oczywiście, zwracając class W C++, język określa, że zostanie wywołany jakiś Destruktor lub konstruktor ruchu, ale jest wiele przypadków, w których może to być zoptymalizowane przez kompilator.

Dodatkowo, Linux x86-64 ABI określa, że zwracanie struct z dwa Skalary (np. wskaźniki, lub long) są wykonywane przez rejestry (%rax & %rdx) więc jest bardzo szybki i skuteczny. Więc w tym konkretnym przypadku jest prawdopodobnie szybsze zwracanie takich dwuskalarnych pól struct niż robienie czegokolwiek innego (np. zapisywanie ich do wskaźnika przekazywanego jako argument).

Zwracanie takiego pola dwuskalarnego struct jest wtedy dużo szybsze niż malloc-ing it and returning a pointer.

 5
Author: Basile Starynkevitch,
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
2015-05-16 07:11:53

Jest to całkowicie legalne, ale przy dużych strukturach należy wziąć pod uwagę dwa czynniki: szybkość i rozmiar stosu.

 4
Author: ebutusov,
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
2012-03-06 19:58:31

Typ struktury może być typem dla wartości zwracanej przez funkcję. Jest to bezpieczne, ponieważ kompilator utworzy kopię struktury i zwróci kopię, a nie lokalną strukturę w funkcji.

typedef struct{
    int a,b;
}mystruct;

mystruct func(int c, int d){
    mystruct retval;
    cout << "func:" <<&retval<< endl;
    retval.a = c;
    retval.b = d;
    return retval;
}

int main()
{
    cout << "main:" <<&(func(1,2))<< endl;


    system("pause");
}
 3
Author: haberdar,
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
2012-03-06 20:15:42

Bezpieczeństwo zależy od sposobu implementacji samej struktury. Po prostu natknąłem się na to pytanie podczas wdrażania czegoś podobnego, a oto potencjalny problem.

Kompilator zwracając wartość wykonuje kilka operacji (m.in.]}

  1. wywołuje Konstruktor kopiującymystruct(const mystruct&) (this jest zmienną tymczasową poza funkcją func przydzieloną przez sam kompilator)
  2. wywołuje destruktor ~mystruct na zmiennej, która była alokowane wewnątrz func
  3. wywołuje mystruct::operator= jeśli zwracana wartość jest przypisana do czegoś innego z =
  4. wywołuje destruktor ~mystruct na zmiennej tymczasowej używanej przez kompilator

Teraz, jeśli mystruct jest tak proste, jak to opisane tutaj wszystko jest w porządku, ale jeśli ma wskaźnik (jak char*) lub bardziej skomplikowane zarządzanie pamięcią, to wszystko zależy od tego, jak mystruct::operator=, mystruct(const mystruct&), i ~mystruct są zaimplementowane. Dlatego proponuję przestrogi przy zwracaniu złożonych struktur danych jako wartość.

 3
Author: Filippo,
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
2013-05-31 08:08:05

Zwracanie struktury jest całkowicie bezpieczne, tak jak to zrobiłeś.

Bazując jednak na tym oświadczeniu: ponieważ rozumiem, że gdy zakres funkcji się skończy, jakikolwiek stos został użyty do przydzielenia struktury może zostać nadpisany, wyobrażam sobie tylko scenariusz, w którym którykolwiek z członków struktury został przydzielony dynamicznie (malloc 'ED lub new' ED), w którym to przypadku, bez RVO, dynamicznie przydzielone elementy zostaną zniszczone, a zwrócona kopia będzie miała przypisaną wartość. członek wskazujący na śmieci.

 3
Author: maress,
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
2015-05-16 07:25:07

Zgadzam się również z sftrabbit, życie rzeczywiście się kończy i obszar stosu zostaje wyczyszczony, ale kompilator jest wystarczająco inteligentny, aby zapewnić, że wszystkie dane powinny być pobierane w rejestrach lub w inny sposób.

Prosty przykład potwierdzenia znajduje się poniżej.(zaczerpnięte z kompilatora MinGW)

_func:
    push    ebp
    mov ebp, esp
    sub esp, 16
    mov eax, DWORD PTR [ebp+8]
    mov DWORD PTR [ebp-8], eax
    mov eax, DWORD PTR [ebp+12]
    mov DWORD PTR [ebp-4], eax
    mov eax, DWORD PTR [ebp-8]
    mov edx, DWORD PTR [ebp-4]
    leave
    ret

Możesz zobaczyć, że wartość b została przekazana przez edx. podczas gdy domyślny eax zawiera wartość dla a.

 2
Author: perilbrain,
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
2012-03-06 20:08:55

Nie jest bezpiecznie zwracać struktury. Uwielbiam to robić sam, ale jeśli ktoś doda Konstruktor kopiujący do zwracanej struktury później, zostanie wywołany Konstruktor kopiujący. Może to być nieoczekiwane i może złamać kod. Ten błąd jest bardzo trudny do znalezienia.

Miałem bardziej rozbudowaną odpowiedź, ale moderatorowi się to nie spodobało. Więc, za Twoją cenę, mój napiwek jest krótki.

 1
Author: Igor Polk,
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
2013-08-10 18:17:43

Dodajmy drugą część do pytania: czy to różni się w zależności od kompilatora?

Rzeczywiście tak jest, jak odkryłem na mój ból: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/

Używałem gcc na win32 (mingw) do wywoływania interfejsów COM, które zwracały struktury. Okazuje się, że MS robi to inaczej niż GNU i tak mój (gcc) program rozbił się ze zmiażdżonym stosem.

Może być tak, że MS może mieć wyższy grunt tutaj - ale wszystko, co mnie obchodzi, to Kompatybilność ABI pomiędzy MS i GNU do budowania w systemie Windows.

Jeśli tak, to jakie jest zachowanie najnowszych wersji kompilatorów dla komputerów stacjonarnych: gcc, g++ i Visual Studio

Możesz znaleźć kilka wiadomości na liście dyskusyjnej Wine o tym, jak MS wydaje się to robić.

 1
Author: effbiae,
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
2015-05-16 07:23:34