[C++] Funzioni che restituiscono array

mklplo751
Salve, a scuola ( al liceo) stiamo facendo c++ e stavo cercando di fare una funzione inserita la dimensione del vettore mi restituisse il vettore; tuttavia, ogni volta che lo faccio mi esce l'errore "segmentation fault". Se non vi reca disturbo, potreste spiegarmi dove sbaglio?
Il codice è questo:
#include <iostream>
using namespace std;

float* costruzione(int i,int v){
    float a[i];
    for(int x=0;x<i;x++){
        cout<<"Inserire la componente numero "<<x+1<<" del vettore numero "<<v<<endl;
        cin>>a[x];
    }
    return a;
}
int main (){
    int i;
    cout<<"Inserire la dimensione del vettore"<<endl;
    cin>>i;
    float *a;
    float *b;
    a=costruzione(i,1);
    b=costruzione(i,2);
    for(int j=0;j<i;j++){
        cout<<a[i]+b[i]<<endl;
    }
    system("Pause");
    return 0;
}

Risposte
apatriarca
La vita del tuo array è limitato alla funzione [tt]costruzione[/tt] ed è quindi invalido cercare di accedere alla sua memoria una volta usciti da questa. Non puoi insomma restituire un puntatore ad una variabile locale ad una funzione. Il fatto che questa variabile sia un array non cambia nulla. La dichiarazione del tuo array è inoltre invalida in C++, ma supportata come estensione da alcuni compilatori. Lo standard C++ richiede che la dimensione di un array sia un'espressione costante. Un argomento alla funzione non è certamente costante. Il tuo programma potrebbe quindi compilare o meno a seconda del compilatore scelto e dagli argomenti passati a questi compilatori.

Siccome vuoi restituire tale array non ha comunque senso allocare staticamente il tuo array nella funzione. Il metodo corretto è quello di usare l'allocazione dinamica o passare l'array (quindi già creato con la dimensione corretta nel main) alla funzione.

Devi insomma fare qualcosa come il seguente:
#include <iostream>

float* costruzione(int i, int v)
{
    float *a = new float[i];
    for (int x = 0; x < i; x++) {
        std::cout << "Inserire la componente numero " << x + 1 << " del vettore numero " << v << std::endl;
        std::cin >> a[x];
    }
    return a;
}

int main ()
{
    std::cout << "Inserire la dimensione del vettore" << std::endl;
    
    int i;
    std::cin >> i;
    
    float *a = costruzione(i, 1);
    float *b = costruzione(i, 2);

    for (int j = 0; j < i; j++) {
        std::cout << a[i] + b[i] << std::endl;
    }

    // Devi deallocare la memoria qui..
    delete[] a;
    delete[] b;
}


Ovviamente c'è anche la possibilità di usare qualcosa come un [tt]std::vector[/tt] in C++ "moderno" ma il tuo professore è certamente rimasto un po' indietro.

mklplo751
Grazie per la risposta.
Volevo chiderti, dato che la prof non ha ancora spiegato i puntatori in modo dettagliato (solo un piccolissimo accenno), cosa intendi per "un puntatore ad una variabile locale ad una funzione" e "deallocare"? E cosa vuol dire le stringe "float *a=new float " ?

apatriarca
Un puntatore è quello che hai usato per restituire il tuo array. E' una variabile che contiene un indirizzo in memoria. Se questo indirizzo non è valido, allora la tua applicazione andrà in crash con l'errore segmentation fault. Ogni variabile ha una sua vita/durata che è determinata dal blocco in cui si trova (qualcosa delimitato da parentesi graffe). Se definisci una variabile all'interno di una funzione, questa variabile non esisterà più una volta che l'esecuzione della funzione è terminata. Il corrispondente indirizzo di memoria sarà quindi invalido e accedervi porta ad un crash del programma come è successo nel tuo caso.

Quando passi un array ad una funzione o lo restituisci da essa, l'array diventa un puntatore al suo primo elemento. La vita della variabile non viene però incrementata. L'array terminerà di esistere in ogni caso alla fine del blocco di codice che lo contiene e quindi il puntatore che restituisci sarà invalido.

L'allocazione dinamica della memoria permette di avere blocchi di memoria che hanno un vita più lunga del blocco che li contiene. La durata di questa memoria parte da quella che viene chiamata "allocazione" (la riga con new che non capisci) e la sua deallocazione (la riga con delete). Se non puoi usare tale funzionalità del linguaggio allora l'unica possibilità è quella di usare la funzione solo per l'inizializzazione dei valori e non per la creazione della variabile.

Qualcosa come il seguente (nota che rimane il commento sul fatto che non è standard):
#include <iostream>

void costruzione(float *a, int i, int v)
{
    for (int x = 0; x < i; x++) {
        std::cout << "Inserire la componente numero " << x + 1 << " del vettore numero " << v << std::endl;
        std::cin >> a[x];
    }
}

int main ()
{
    std::cout << "Inserire la dimensione del vettore" << std::endl;
   
    int i;
    std::cin >> i;
   
    float a[i];
    costruzione(a, i, 1);

    float b[i];
    costruzione(b, i, 2);

    for (int j = 0; j < i; j++) {
        std::cout << a[i] + b[i] << std::endl;
    }
}

mklplo751
Ok, grazie nuovamente. Quindi se ho capito bene, se nel primo programma avvessi provato a richiamare il puntatore dopo il "delete" avrei avuto un errore perché sarebbe finita la sua durata giusto?
Grazie anche per il secondo programma, non avrei mai pensato a un modo del genere di usare le procedure.

apatriarca
Esatto. Anche se a volte la memoria viene riutilizzata e invece di un crash ottieni valori errati. Ma l'idea è quella.

mklplo751
Ok, grazie nuovamente.

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.