W C, jak mam wybrać, czy zwracać strukturę, czy wskaźnik do struktury?

Praca nad moim mięśniem C Ostatnio i przeglądanie wielu bibliotek, z którymi pracowałem, z pewnością dało mi dobry pomysł na to, co jest dobrą praktyką. Jedna rzecz, której nie widziałem, to funkcja zwracająca strukturę:

something_t make_something() { ... }

Z tego co wchłonąłem to jest "właściwy" sposób na to:

something_t *make_something() { ... }
void destroy_something(something_t *object) { ... }

Architektura kodu snippet 2 jest znacznie bardziej popularna niż snippet 1. Więc teraz pytam, dlaczego miałbym zwracać struct bezpośrednio, jak w snippet 1? Jakie różnice powinny Biorę pod uwagę, gdy wybieram między tymi dwoma opcjami?

Ponadto, jak ta opcja porównuje?

void make_something(something_t *object)
Author: Sanchke Dellowar, 2016-10-21

6 answers

Gdy something_t jest mały (Czytaj: kopiowanie jest mniej więcej tak tanie jak kopiowanie wskaźnika) i chcesz, aby był domyślnie przypisany do stosu:

something_t make_something(void);

something_t stack_thing = make_something();

something_t *heap_thing = malloc(sizeof *heap_thing);
*heap_thing = make_something();

Gdy something_t jest duże lub chcesz, aby zostało przydzielone:

something_t *make_something(void);

something_t *heap_thing = make_something();

Niezależnie od wielkości something_t, a jeśli nie obchodzi cię, gdzie jest przydzielona:

void make_something(something_t *);

something_t stack_thing;
make_something(&stack_thing);

something_t *heap_thing = malloc(sizeof *heap_thing);
make_something(heap_thing);
 57
Author: Jon Purdy,
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
2016-10-21 04:16:24

Prawie zawsze chodzi o stabilność ABI. Stabilność binarna pomiędzy wersjami biblioteki. W przypadkach, w których tak nie jest, czasami chodzi o dynamiczne struktury. Rzadko chodzi o ekstremalnie Duże structs lub wydajność.


Niezmiernie rzadko zdarza się, że przydzielanie struct na stertę i zwracanie jej jest prawie tak szybkie,jak zwracanie jej według wartości. struct musiałby być ogromny.

Naprawdę, prędkość nie jest powodem techniki 2, return-by-pointer, zamiast return-by-value.

Technika 2 istnieje dla stabilności ABI. Jeśli masz struct i Twoja następna wersja biblioteki dodaje do niej kolejne 20 pól, konsumenci poprzedniej wersji biblioteki są kompatybilni binarnie , jeśli są podanymi wstępnie skonstruowanymi wskaźnikami. Dodatkowe dane poza końcem struct, o których wiedzą, są czymś, o czym nie muszą wiedzieć.

Jeśli zwrócisz go na stosie, wywołujący alokuje pamięć za to, i muszą się z tobą zgodzić, jak duży jest. Jeśli biblioteka została zaktualizowana od czasu ich ostatniej przebudowy, możesz zniszczyć stos.

Technika 2 pozwala również na ukrycie dodatkowych danych zarówno przed, jak i po zwracanym wskaźniku(które wersje dołączające dane na końcu struktury są wariantem). Możesz zakończyć strukturę tablicą o zmiennej wielkości lub poprzedzić wskaźnik dodatkowymi danymi lub obiema.

Jeśli chcesz przypisać stack struct s w stabilnym ABI, prawie wszystkie funkcje, które rozmawiają z struct, muszą zostać przekazane informacje o wersji.

Więc

something_t make_something(unsigned library_version) { ... }

Gdzie library_version jest używana przez bibliotekę do określenia, jaka wersja something_tma zostać zwrócona, a zmienia ilość stosu, którą manipuluje. Nie jest to możliwe przy użyciu standardowego C, ale

void make_something(something_t* here) { ... }

Jest. W tym przypadku, {[10] } może mieć pole version jako pierwszy element( lub pole rozmiaru), i wymagałbyś, aby zostało wypełnione przed wywołaniem make_something.

Inny kod biblioteki pobierający something_t zapyta pole version, aby określić, z jaką wersją something_t pracują.

 36
Author: Yakk - Adam Nevraumont,
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
2016-11-17 16:19:49

Zgodnie z zasadą, nigdy nie należy przekazywać struct obiektów według wartości. W praktyce będzie to w porządku, jeśli są one mniejsze lub równe maksymalnemu rozmiarowi, jaki procesor może obsłużyć w jednej instrukcji. Ale stylistycznie, zazwyczaj unika się tego nawet wtedy. Jeśli nigdy nie przekażesz struktury według wartości, możesz później dodać członków do struktury i nie wpłynie to na wydajność.

Myślę, że {[1] } jest najczęstszym sposobem wykorzystania struktur w C. pozostawiasz przypisanie do dzwoniący. Jest wydajny, ale nie ładny.

Jednak zorientowane obiektowo programy C używają something_t *make_something(), ponieważ są zbudowane z koncepcją typu nieprzezroczystego , co zmusza do używania wskaźników. To, czy zwracany wskaźnik wskazuje na pamięć dynamiczną, czy coś innego, zależy od implementacji. Oo z nieprzezroczystym typem jest często jednym z najbardziej eleganckich i najlepszych sposobów projektowania bardziej złożonych programów C, ale niestety niewielu programistów C O tym wie/troszczy się.

 13
Author: Lundin,
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
2016-10-21 06:54:32

Niektóre plusy pierwszego podejścia:

  • mniej kodu do napisania.
  • bardziej idiomatyczny dla przypadku użycia zwracania wielu wartości.
  • działa na systemach, które nie mają dynamicznej alokacji.
  • prawdopodobnie szybciej dla małych lub małych obiektów.
  • Brak wycieku pamięci z powodu zapomnienia o free.

Niektóre wady:

  • jeśli obiekt jest duży (powiedzmy megabajt), może powodować przepełnienie stosu lub może być powolny, jeśli Kompilatory go nie optymalizują cóż.
  • Może zaskoczyć ludzi, którzy nauczyli się C w latach 70., gdy nie było to możliwe i nie byli na bieżąco.
  • nie działa z obiektami, które zawierają wskaźnik do części siebie.
 9
Author: M.M,
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
2016-10-21 04:44:43

Jestem trochę zaskoczony.

Różnica polega na tym, że przykład 1 tworzy strukturę na stosie, przykład 2 tworzy ją na stosie. W C, lub C++ kod, który jest efektywnie C, idiomatyczne i wygodne do tworzenia większości obiektów na stercie. W C++ to nie jest, przeważnie idą na stos. Powodem jest to, że jeśli utworzysz obiekt na stosie, Destruktor zostanie wywołany automatycznie, jeśli utworzysz go na stosie, musi zostać wywołany explicitly.So o wiele łatwiej jest zapewnić, że nie ma wycieki pamięci i do obsługi wyjątków jest wszystko idzie na stosie. W języku C destructor i tak musi być wywołany wprost, a nie ma pojęcia o specjalnej funkcji destructor (oczywiście masz destructor, ale są to zwykłe funkcje o nazwach takich jak destroy_myobject()).

Teraz wyjątek w C++ dotyczy obiektów kontenerów niskiego poziomu, np. wektorów, drzew, map skrótów itd. Te zachowują członków sterty i mają destruktory. Obecnie większość obiektów obciążonych pamięcią składa się z kilka natychmiastowych członków danych podając rozmiary, identyfikatory, tagi itp., a następnie resztę informacji w strukturach STL, może wektor danych pikselowych lub mapę angielskich par słowo / wartość. Tak więc większość danych jest w rzeczywistości na stercie, nawet w C++.

I nowoczesny C++ jest zaprojektowany tak, aby ten wzór

class big
{
    std::vector<double> observations; // thousands of observations
    int station_x;                    // a bit of data associated with them
    int station_y; 
    std::string station_name; 
}  

big retrieveobservations(int a, int b, int c)
{
    big answer;
    //  lots of code to fill in the structure here

    return answer;
}

void high_level()
{
   big myobservations = retriveobservations(1, 2, 3);
}

Skompiluje się do całkiem wydajnego kodu. Duży członek obserwacyjny nie wygeneruje niepotrzebnych kopii makework.

 4
Author: Malcolm McLean,
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
2016-10-21 04:08:49

W przeciwieństwie do innych języków (jak Python), C nie ma pojęcia krotki . Na przykład w Pythonie legalne jest:

def foo():
    return 1,2

x,y = foo()
print x, y

Funkcja foo zwraca dwie wartości jako krotkę, które są przypisane do x i y.

Ponieważ C nie ma pojęcia krotki, niewygodne jest zwracanie wielu wartości z funkcji. Jednym ze sposobów obejścia tego problemu jest zdefiniowanie struktury, która przechowuje wartości, a następnie zwróci strukturę, jak to:

typedef struct { int x, y; } stPoint;

stPoint foo( void )
{
    stPoint point = { 1, 2 };
    return point;
}

int main( void )
{
    stPoint point = foo();
    printf( "%d %d\n", point.x, point.y );
}

To tylko jeden przykład, w którym funkcja zwraca strukturę.

 3
Author: user3386109,
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
2016-10-21 03:11:13