HOWTO · C++

Zeiger auf Array in C++ zurückgeben

Dieser Artikel demonstriert, wie man einen Zeiger auf ein Array aus einer Funktion in C++ zurückgeben

Auf dieser Seite

Eine C++-Funktion kann einen Zeiger auf ein Arrayelement zurückgeben. Der Zeiger enthält jedoch weder die Länge des Arrays noch Angaben dazu, wem der Speicher gehört. Für eine sichere Verwendung müssen Sie wissen, welches Objekt den Speicher besitzt, wie viele Elemente verfügbar sind und wie lange dieses Objekt lebt. Für gewöhnliche Rückgabewerte ist es meist klarer, einen Container als Wert zurückzugeben, statt einen rohen Zeiger weiterzugeben.

Verstehen, worauf ein Arrayzeiger verweist

Bei einem Funktionsparameter wird int values[] zu int* values angepasst. Die feste Arraylänge bleibt dabei nicht erhalten. Übergeben Sie daher die Elementanzahl separat oder verwenden Sie einen Container, der seine Größe kennt. Der Zeiger verweist auf das erste Element. Ein Zugriff mit values[i] ist nur dann zulässig, wenn das Element zum selben Array gehört und der Index innerhalb seiner Grenzen liegt.

Mit der Rückgabe von values geben Sie diese Adresse zurück. Das Array wird dadurch weder kopiert noch seine Lebensdauer verlängert. Bei einem std::array-Objekt wie values liefert der Aufruf von values.data() einen Zeiger auf dessen Elemente. Dieser Zeiger bleibt nur so lange gültig, wie das Objekt values lebt und sein Speicher nicht ungültig geworden ist.

Einen Zeiger auf ein Array zurückgeben, das dem Aufrufer gehört

Ein nützliches Muster mit rohen Zeigern besteht darin, das Array vom Aufrufer verwalten zu lassen und seine Adresse sowie Länge an eine Funktion zu übergeben. Die Funktion kann die Elemente ändern und denselben Zeiger zurückgeben, damit der Aufrufer ihn bequem weiterverwenden kann. Der Aufrufer muss das Array am Leben und seinen Speicher unverändert halten, solange der zurückgegebene Zeiger verwendet wird.

Führen Sie dieses Beispiel aus. Der Code und die numerische Ausgabe bleiben unverändert:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

#include <cstddef>
#include <iostream>

int* subtractFrom(int values[], std::size_t size, int amount) {
    for (std::size_t i = 0; i < size; ++i) {
        values[i] -= amount;
    }
    return values;
}

int main() {
    int values[] = {5, 10, 15};
    int* result = subtractFrom(values, 3, 2);

    for (std::size_t i = 0; i < 3; ++i) {
        if (i != 0) std::cout << ' ';
        std::cout << result[i];
    }
    std::cout << '\n';
}
3 8 13

Das Array gehört zu main und lebt daher noch, wenn dort result gelesen wird. Die Größe wird separat übergeben, da der Funktionsparameter die ursprüngliche Arraylänge nicht kennt. Für einen Container mit fester Größe bietet std::array<int, 3> values ein größenbewusstes Objekt. Seine data()-Methode kann einer Schnittstelle, die einen Zeiger benötigt, einen Zeiger auf die Elemente liefern.

Einen Zeiger auf dynamisch reservierten Arrayspeicher zurückgeben

Eine Funktion kann mit new[] Speicher reservieren und den resultierenden Zeiger zurückgeben. Damit übernimmt der Aufrufer die manuelle Freigabe: Er muss den Zeiger behalten, darf höchstens auf die reservierte Anzahl an Elementen zugreifen und muss den Speicher nach der Verwendung genau einmal mit delete[] freigeben. Auch die Elementanzahl muss dem Aufrufer bekannt sein, da ein roher Zeiger sie nicht speichert. new[] mit einfachem delete zu kombinieren, den Speicher zweimal freizugeben oder die Freigabe zu vergessen, führt zu Fehlern oder Speicherlecks.

In diesem Beispiel reserviert makeSequence Speicher für drei Ganzzahlen und füllt ihn. Der Aufrufer erhält den Zeiger, verwendet die bekannte Elementanzahl und gibt das Array mit dem passenden delete[] frei. Der Code und die numerischen Ausgabewerte bleiben unverändert:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

#include <cstddef>
#include <iostream>

int* makeSequence(std::size_t count) {
    int* values = new int[count];
    for (std::size_t i = 0; i < count; ++i) {
        values[i] = static_cast<int>(i * 5 + 3);
    }
    return values;
}

int main() {
    const std::size_t count = 3;
    int* values = makeSequence(count);

    for (std::size_t i = 0; i < count; ++i) {
        std::cout << values[i] << '\n';
    }

    delete[] values;
}

Diese Technik eignet sich für eine Laufzeitgröße, doch der Überblick über die Besitzverhältnisse geht leicht verloren. Für ein Ergebnis variabler Länge empfiehlt es sich, std::vector<int> als Wert zurückzugeben. Der Vektor verwaltet seinen Speicher, stellt seine Größe bereit und lässt sich effizient verschieben. Der Aufrufer sollte seine Elemente nur verwenden, solange der Vektor lebt und sein Speicher nicht durch eine Operation ungültig geworden ist, die eine Neuzuweisung auslöst.

Ungültige Zeiger vermeiden und einen sichereren Rückgabetyp wählen

Geben Sie keinen Zeiger auf ein automatisches lokales Array zurück. Dessen Lebensdauer endet, sobald die Funktion zurückkehrt; der Aufrufer hätte dann einen ungültigen Zeiger. Anders verhält es sich mit einem lokalen static-Array: Sein Speicher bleibt während der gesamten Programmlaufzeit erhalten. Der Zeiger wird also nicht allein dadurch ungültig, dass die Funktion zurückkehrt. Allerdings verwenden alle Aufrufe dasselbe veränderbare Array, seine Kapazität ist fest, und verschachtelte oder nebenläufige Zugriffe können sich in die Quere kommen. Verwenden Sie statischen Speicher nur, wenn der gemeinsame Zustand beabsichtigt ist.

Geben Sie ein Ergebnis fester Größe als std::array<int, N> zurück, ein Ergebnis variabler Größe als std::vector<int>. Diese Typen machen die Besitzverhältnisse klar und ersparen es dem Aufrufer, einen rohen Zeiger mit einer separaten Freigaberegel zu kombinieren. Muss eine Schnittstelle einen Zeiger auf ein Element zurückgeben, dokumentieren Sie das besitzende Objekt und die Gültigkeitsdauer des Zeigers. Verwenden Sie ihn nicht mehr, nachdem sein Besitzer zerstört wurde oder sich dessen Speicher geändert hat.