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)
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);
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 struct
s 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_t
ma 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ą.
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ę.
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.
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.
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ę.
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