2009-04-29 8 views
4

Ich überprüfte den Code eines Freundes und kam in eine interessante Debatte darüber, wie C/C++ Speicher auf dem Stack zuweist und seine Freigabe verwaltet. Wenn ich ein Array von 10 Objekten in einer Funktion erstellen, aber das Array zurückgeben würde, wird es freigegeben, wenn die Funktion auftaucht (wodurch die gegebenen Daten ungültig werden) oder wird es in den Heap platziert (was die Frage aufwirft, wie wir das machen) Lass es los?).Stapeldaten in C zurückgeben; Richtet es sich korrekt aus?

Beispielcode wie folgt:

Gene* GetTopTen() 
{ 
    // Create 10 genes (or 10 objects, doesn't matter) 
    Gene Ten[10]; 

    // Sort out external pool data 
    Sort(); 

    // Copy over data to the array of 10 objects 
    for(int i = 0; i < 10; Ten[i++] = pool[i]); 

    // Here is the core of my question: 
    return Ten; 
} 

Jede Hilfe wird sehr geschätzt, das ist meine Freunde in eine sehr interessante Frage drehen, und ich kann nicht antworten.

+3

Nur ein FYI - der 3. Ausdruck in Ihrer 'for' Anweisung, Ten [i ++] = pool [i]', hat undefiniertes Verhalten, weil Sie mehr als einen gespeicherten Wert lesen, der in einem Ausdruck geändert wird. –

Antwort

22

Das ist ein Array, dem ein Stack zugewiesen wurde, daher ist der zurückgegebene Zeiger ungültig.

+3

Und ein guter Compiler wird vor dieser Situation warnen. –

3

Das in undefinierten Verhalten führt. zehn Array ist im Stapel gespeichert. Sobald die GetTopGene-Funktion beendet ist, ist dieser Stapel zerstört, also sollten Sie keine Zeiger auf diesen Teil des Speichers verwenden.

Sie möchten Speicherplatz im Heap reservieren.

Gene* GetTopTen(){ 
     Gene* genes = (Gene*) malloc (10*sizeof(Gene)); 
     //do stuff. 
     return genes; 
} 

Sie müssen daran denken, diesen Speicher freizugeben, sobald Sie damit fertig sind.

Gene* genes = GetTopTen(); 
    //do stuff with it. 
    free(genes); 
+1

"Sie möchten Speicherplatz im Heap reservieren." Ich würde sagen, was Sie tun wollen, ist das Ziel-Array als Parameter zu nehmen ;-) –

1

Wenn Sie ein Array aus einer Funktion zurückkehren wollen, müssen Sie es auf dem Heap selbst setzen müssen:

In C

Gene* GetTopTen() 
{ 
    // Create 10 genes (or 10 objects, doesn't matter) 
    Gene *Ten = malloc(sizeof(Gene)*10); 
    ....  
    // Now it's ok to return 
    return Ten; 
} 

int main() 
{ 
    Gene *genes = GetTopTen(); 
    free (genes); 
} 

Und in C++:

Gene* GetTopTen() 
{ 
    // Create 10 genes (or 10 objects, doesn't matter) 
    Gene *Ten = new Gene[10]; 
    ....  
    // Now it's ok to return 
    return Ten; 
} 

int main() 
{ 
    Gene *genes = GetTopTen(); 
    delete [] genes; 
} 

Natürlich liegt es auch an Ihnen, zu verfolgen, wie lange dieses Array ist, indem Sie entweder eine Länge von GetTopTen in einem Zeiger zurückgeben/Referenzparameter oder durch irgendeine Art von Konstante.

+1

Wenn Sie eine C++ - Alternative zur Verfügung stellen, empfehle ich die Rückgabe eines std :: vector ist eine bessere Idee. –

+0

Das ist nicht auf dem Stapel, das ist auf dem Haufen! – dreamlax

+0

sollte sein: "... Sie müssen es auf den HEAP selbst setzen." und die Malloc-Linie ist an zwei Stellen unterbrochen. – Dan

9

Sobald die Funktion zurückkehrt, werden die Daten nicht zerstört, werden aber wahrscheinlich beim nächsten Funktionsaufruf überschrieben. Sie können Glück haben und es kann immer noch da sein, aber es ist undefiniertes Verhalten. Du solltest dich nicht darauf verlassen.

Hier ist, was passieren kann: während der Funktion der Stapel sieht wie folgt aus:

"--------------------------- 
| caller function's data | 
---------------------------- 
| Ten[9]     | 
| Ten[8]     | 
| ...      | 
| Ten[0]     | 
---------------------------" 

Unmittelbar nach der Funktion beendet wird, wird es wahrscheinlich gleich aussehen. Aber wenn der Anrufer ruft eine andere Funktion wie diese,

void some_func() { 
    Gene g; 
    ... 
} 

der Stapel wird nun wie folgt aussehen:

"--------------------------- 
| caller function's data | 
---------------------------- 
| g      | 
---------------------------- 
| Ten[8]     | 
| ...      | 
| Ten[0]     | 
---------------------------" 

Einige der Daten geräuschlos überschrieben werden können (in diesem Fall ist es Ten[9]) und Dein Code wird es nicht wissen. Sie sollten die Daten im Heap mit malloc() belegen und explizit mit free() freigeben.

+1

Nur um explizit zu sein, kann der Speicher vor dem nächsten Funktionsaufruf überschrieben werden. Viele Dinge drängen auf den Stapel. –

+0

Deshalb habe ich 'wahrscheinlich' gesagt. Nach meiner Erfahrung ist es normalerweise wahr. – Zifre

0

Ihre Funktion sagt, dass sie einen Zeiger zurückgibt. Wenn Ihre Funktion aufgerufen wird, wird nur Platz für einen Zeiger auf dem Stapel für den Rückgabewert reserviert.Daher ist der Rückgabewert ein Zeiger, der auf eine Stelle auf dem Stapel verweist, die beim Beenden der Funktion ungültig ist.

Wickeln Sie Ihren Rückgabewert in eine struct.

typedef struct 
{ 
    int Array[10]; 
} ArrayOfTen; 

ArrayOfTen SomeFunction() 
{ 
    ArrayOfTen array = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; 
    return array; 
} 

int main (int argc, char *argv[]) 
{ 
    ArrayOfTen array = SomeFunction(); 
    int i; 

    for (i = 0; i < 0; i++) 
     fprintf (stdout, "%d\n", array.Array[i]); 

    return 0; 
} 
2

Dies ist die Ursache für die schlimmsten Bugs! Manchmal wird es funktionieren, manchmal nicht.

Dies ist ein Fehler, kein "cleveres Stück Code".

2

Ein anderer Weg, dies richtig zu tun, ohne Heap-Speicher zuzuordnen ist, ist die Funktion des Prototyp zu verändern, um einen Zeiger auf das Array zu nehmen das Ergebnis in schreiben:

Hohlraum GetTopTen (Gen Ten []) { ...}

dann löschen Sie einfach die Deklaration von Zehn im Funktionskörper, da es jetzt ein Parameter ist.

Der Anrufer muss nun seine eigene zehn Elementanordnung deklarieren und als Parameter übergeben:

... Gene oben [10]; GetTopTen (oben); ...

Seien Sie vorsichtig, dass der Anrufer eine ausreichend große Array deklariert! Unglücklicherweise hat C keine gute Möglichkeit, die Größe des Arrays anzugeben, das übergeben werden soll. Der Compiler wird Sie also nicht warnen, wenn der Aufrufer ein zu kleines Array deklariert. Es wird nur den Stapel zur Laufzeit überschreiben.

+1

Achten Sie darauf, die Größe dieses Arrays explizit zu übergeben, da Sie sonst einen anderen Fehler riskieren, wenn jemand versehentlich ein Array anderer Größe passiert, ohne darüber nachzudenken. – johnny

0

Dies ist kein Fehler, dies ist nur eine niedrige Sprache. Sie können das Array (fast) nicht stapeln und in der gleichen Funktion zurückgeben - bis Sie nicht zu den (Un) luckies gehören. Aber Sie sollten malloc nicht in jeder Situation anrufen.

Um Ihr Programm speicherfreundlich zu machen, ordnen Sie dieses Array dem Stapel im Aufrufer zu und übergeben Sie es als Argument.

GetTopTen Funktion wird dann aussehen:

void GetTopTen(Gene* genes) { 
    /* 
    Do something 
    Do not allocate new Gene array, you already have one - modify values at "genes" pointer 
    */ 
} 

Nennen Sie es:

Gene genes[10]; 
GetTopTen(genes); 
/* 
Now, do whatever you want with top ten "genes", which are safe until return from this function 
*/ 

Dieser Stil, wie ich heute sehen, nicht verwendet wird, und es ist sehr traurig, dass ich nicht bin ein Scherz, denn Dies ist in dem, was Low-Level-Sprachen von diesen unterscheiden mit Garbage Collection, und auch viel schneller als malloc() die ganze Zeit aufrufen; Diese Variablen werden automatisch freigegeben und benötigen weniger Speicher. Aber beachten Sie, dass es abhängig von der Größe der Struktur und der Anzahl der Elemente ist - wenn es wirklich groß ist, ordnen Sie es über malloc zu, sonst können Sie einen Stapelüberlauf bekommen.