Domanda

Ho scritto C solo per poche settimane e non mi sono preso il tempo di preoccuparmi troppo di malloc () . Di recente, però, un mio programma ha restituito una serie di facce felici invece dei valori veri / falsi che mi aspettavo.

Se creo una struttura come questa:

typedef struct Cell {
  struct Cell* subcells;
} 

e successivamente inizializzalo in questo modo

Cell makeCell(int dim) {
  Cell newCell;

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);
  }

  return newCell; //ha ha ha, this is here in my program don't worry!
}

Finirò per accedere a volti felici memorizzati nella memoria da qualche parte, o forse scrivere su celle precedentemente esistenti, o cosa? La mia domanda è: in che modo C alloca la memoria quando non ho effettivamente malloc () ed la quantità appropriata di memoria? Qual è l'impostazione predefinita?

È stato utile?

Soluzione

Non esiste un valore predefinito per il puntatore. Il puntatore punterà a ciò che memorizza attualmente. Poiché non lo hai inizializzato, la riga

newCell.subcells[i] = ...

Accede efficacemente a una parte incerta della memoria. Ricorda che le sottocellule [i] equivalgono a

*(newCell.subcells + i)

Se il lato sinistro contiene della spazzatura, finirai per aggiungere i a un valore di immondizia e accedere alla memoria in quella posizione incerta. Come hai detto correttamente, dovrai inizializzare il puntatore per puntare ad un'area di memoria valida:

newCell.subcells = malloc(bytecount)

Dopo quale riga è possibile accedere a tanti byte. Per quanto riguarda le altre fonti di memoria, esistono diversi tipi di archiviazione che tutti hanno i loro usi. Il tipo che ottieni dipende dal tipo di oggetto che hai e dalla classe di memoria che dici al compilatore da usare.

  • malloc restituisce un puntatore a un oggetto senza tipo. È possibile puntare un puntatore verso quella regione di memoria e il tipo di oggetto diventerà effettivamente il tipo di tipo di oggetto puntato. La memoria non è inizializzata su alcun valore e l'accesso di solito è più lento. Gli oggetti così ottenuti sono chiamati oggetti allocati .
  • Puoi posizionare oggetti a livello globale. La loro memoria verrà inizializzata a zero. Per i punti, otterrai i puntatori NULL, per i float otterrai anche uno zero corretto. Puoi fare affidamento su un valore iniziale adeguato.
  • Se si hanno variabili locali ma si utilizza l'identificatore di classe di archiviazione statico , si avrà la stessa regola del valore iniziale degli oggetti globali. La memoria di solito è allocata allo stesso modo degli oggetti globali, ma non è assolutamente una necessità.
  • Se hai variabili locali senza alcun identificatore di classe di archiviazione o con auto , la tua variabile verrà allocata nello stack (anche se non definito da C, questo è ciò che i compilatori fanno praticamente ovviamente ). Puoi prendere il suo indirizzo nel qual caso il compilatore dovrà omettere le ottimizzazioni come ovviamente inserendolo nei registri.
  • Le variabili locali utilizzate con l'identificatore della classe di archiviazione register , sono contrassegnate come aventi una memoria speciale. Di conseguenza, non puoi più prendere il suo indirizzo. Negli ultimi compilatori, normalmente non è più necessario utilizzare register , a causa dei loro sofisticati ottimizzatori. Se sei davvero esperto, potresti ottenere delle prestazioni se lo usi, però.

Gli oggetti hanno durate di archiviazione associate che possono essere utilizzate per mostrare le diverse regole di inizializzazione (formalmente, definiscono solo per quanto tempo vivono almeno gli oggetti). Gli oggetti dichiarati con auto e register hanno una durata di memorizzazione automatica e non inizializzati. È necessario inizializzarli esplicitamente se si desidera che contengano un valore. In caso contrario, conterranno qualsiasi cosa il compilatore abbia lasciato in pila prima che iniziassero la vita. Gli oggetti allocati da malloc (o un'altra funzione di quella famiglia, come calloc ) hanno allocato la durata della memoria. Anche il loro archivio è non inizializzato. Un'eccezione è quando si utilizza calloc , nel qual caso la memoria viene inizializzata su zero ("reale" zero. Vale a dire tutti i byte 0x00, indipendentemente da qualsiasi rappresentazione del puntatore NULL). Gli oggetti dichiarati con statico e le variabili globali hanno una durata di archiviazione statica. La loro memoria è inizializzata su zero appropriata per il rispettivo tipo. Si noti che un oggetto non deve avere un tipo, ma l'unico modo per ottenere un oggetto senza tipo è utilizzare l'archiviazione allocata. (Un oggetto in C è una "regione di archiviazione").

Quindi cos'è? Ecco il codice fisso. Perché una volta allocato un blocco di memoria non è più possibile recuperare quanti elementi sono stati allocati, la cosa migliore è conservare sempre quel conteggio da qualche parte. Ho introdotto un dim variale nella struttura che memorizza il conteggio.

Cell makeCell(int dim) {
  /* automatic storage duration => need to init manually */
  Cell newCell;

  /* note that in case dim is zero, we can either get NULL or a 
   * unique non-null value back from malloc. This depends on the
   * implementation. */
  newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
  newCell.dim = dim;

  /* the following can be used as a check for an out-of-memory 
   * situation:
   * if(newCell.subcells == NULL && dim > 0) ... */
  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim - 1);
  }

  return newCell;
}

Ora, le cose sembrano così per dim = 2:

Cell { 
  subcells => { 
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    },
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    }
  },
  dim = 2
}

Si noti che in C, il valore restituito di una funzione non è necessario per essere un oggetto. Non è necessario alcun tipo di archiviazione. Di conseguenza, non ti è permesso di cambiarlo. Ad esempio, non è possibile:

makeCells(0).dim++

Avrai bisogno di una funzione "quot" gratuita " quella libera è di nuovo la memoria allocata. Perché l'archiviazione per gli oggetti allocati non viene liberata automaticamente. Devi chiamare free per liberare quella memoria per ogni puntatore subcells nella tua struttura. È un esercizio per te scriverlo :)

Altri suggerimenti

Risposta breve: non è allocata per te.

Risposta leggermente più lunga: il puntatore subcells non è inizializzato e può puntare ovunque . Questo è un bug e tu non dovrebbe mai permettere che accada.

Risposta ancora più lunga: le variabili automatiche sono allocate nello stack, le variabili globali sono allocate dal compilatore e spesso occupano un segmento speciale o potrebbero essere nell'heap. Le variabili globali sono inizializzate su zero per impostazione predefinita. Le variabili automatiche non hanno un valore predefinito (ottengono semplicemente il valore trovato in memoria) e il programmatore è responsabile di assicurarsi che abbiano buoni valori iniziali (anche se molti compilatori cercheranno di indicarti quando ti dimentichi).

La variabile newCell nella tua funzione è automatica e non è inizializzata. Dovresti risolvere quel pronto. Dai a newCell.subcells un valore significativo prontamente, oppure puntalo su NULL fino a quando non ne assegni uno spazio. In questo modo lancerai una violazione di segmentazione se provi a dereferenziarla prima di allocare memoria per essa.

Ancora peggio, stai restituendo un Cella per valore, ma assegnandolo a una Cella * quando tenti di riempire l'array subcells . Restituisce un puntatore a un oggetto allocato heap oppure assegna il valore a un oggetto allocato localmente.

Un solito linguaggio per questo avrebbe la forma di qualcosa come

Cell* makeCell(dim){
  Cell *newCell = malloc(sizeof(Cell));
  // error checking here
  newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
  // more error checking
  for (int i=0; i<dim; ++i){
    newCell->subCells[i] = makeCell(dim-1);
    // what error checking do you need here? 
    // depends on your other error checking...
  }
  return newCell;
}

anche se ti ho lasciato alcuni problemi da risolvere ...

E nota che devi tenere traccia di tutti i bit di memoria che alla fine dovranno essere deallocati ...

Tutto ciò che non è stato allocato nell'heap (tramite malloc e chiamate simili) è invece allocato nello stack. Per questo motivo, qualsiasi cosa creata in una particolare funzione senza essere malloc 'd verrà distrutta al termine della funzione. Ciò include gli oggetti restituiti; quando lo stack viene srotolato dopo una chiamata di funzione, l'oggetto restituito viene copiato nello spazio accantonato nello stack dalla funzione chiamante.

Avviso: se vuoi restituire un oggetto che ha puntatori ad altri oggetti al suo interno, assicurati che gli oggetti a cui punta siano creati sull'heap e, ancora meglio, crea quell'oggetto sul anche heap, a meno che non sia destinato a sopravvivere alla funzione in cui è stato creato.

  

La mia domanda è: in che modo C alloca la memoria quando non ho effettivamente malloc () ed la quantità appropriata di memoria? Qual è l'impostazione predefinita?

Per non allocare memoria. Devi esplicitamente crearlo in pila o dinamicamente.

Nel tuo esempio, le sottocellule indicano una posizione indefinita , che è un bug. La tua funzione dovrebbe restituire un puntatore a una struttura Celle ad un certo punto.

  

Finirò per accedere a volti felici memorizzati nella memoria da qualche parte, o forse scrivere su celle precedentemente esistenti, o cosa?

Sei fortunato ad avere una faccia felice. In uno di quei giorni sfortunati, avrebbe potuto ripulire il tuo sistema;)

  

La mia domanda è: in che modo C alloca la memoria quando non ho effettivamente malloc () ed la quantità appropriata di memoria?

Non lo fa. Tuttavia, ciò che accade è quando si definisce Cell newCell, il puntatore subCells viene inizializzato sul valore di Garbage. Che può essere uno 0 (nel qual caso si otterrebbe un arresto anomalo) o un numero intero abbastanza grande da farlo sembrare un indirizzo di memoria reale. Il compilatore, in questi casi, recupererebbe felicemente qualunque valore risieda lì e te lo riporti.

  

Qual è l'impostazione predefinita?

Questo è il comportamento se non si inizializzano le variabili. E la tua funzione makeCell sembra un po 'sottosviluppata.

Esistono in realtà tre sezioni in cui è possibile allocare le cose: dati, stack e amp; mucchio.

Nel caso menzionato, verrebbe allocato nello stack. Il problema con l'allocazione di qualcosa nello stack è che è valido solo per la durata della funzione. Quando la funzione ritorna, quella memoria viene recuperata. Pertanto, se si restituisce un puntatore a qualcosa allocato nello stack, quel puntatore non sarà valido. Se restituisci l'oggetto reale (non un puntatore), verrà automaticamente creata una copia dell'oggetto da utilizzare per la funzione chiamante.

Se l'avessi dichiarato come variabile globale (ad es. in un file di intestazione o al di fuori di una funzione), sarebbe allocato nella sezione dati della memoria. La memoria in questa sezione viene allocata automaticamente all'avvio del programma e allocata automaticamente al termine.

Se si alloca qualcosa sull'heap usando malloc (), quella memoria è valida per tutto il tempo in cui si desidera utilizzarla - fino a quando non si chiama free () a quel punto viene rilasciata. Questo ti dà la flessibilità di allocare e deallocare la memoria quando ne hai bisogno (invece di usare i globali dove tutto è allocato in anticipo e rilasciato solo quando il tuo programma termina).

Le variabili locali sono " allocate " sullo stack. Lo stack è una quantità di memoria preallocata per contenere quelle variabili locali. Le variabili cessano di essere valide quando la funzione esce e verranno sovrascritte da qualunque cosa accada.

Nel tuo caso, il codice non sta facendo nulla poiché non restituisce il tuo risultato. Inoltre, un puntatore a un oggetto nello stack cesserà di essere valido quando esce l'ambito, quindi immagino nel tuo caso preciso (sembra che tu stia facendo un elenco collegato), dovrai usare malloc ().

Farò finta di essere il computer qui, leggendo questo codice ...

typedef struct Cell {
  struct Cell* subcells;
}

Questo mi dice:

  • Abbiamo un tipo di struttura chiamato Cell
  • Contiene un puntatore chiamato sottocellule
  • Il puntatore dovrebbe essere qualcosa di tipo struct Cell

Non mi dice se il puntatore si sposta su una cella o su una matrice di celle. Quando viene creata una nuova cella, il valore di quel puntatore non è definito fino a quando non viene assegnato un valore. È una cattiva notizia usare i puntatori prima di definirli.

Cell makeCell(int dim) {
  Cell newCell;

Nuova struttura della cella, con un puntatore a sottocelle non definito. Tutto ciò che fa è riservare un piccolo pezzo di memoria a essere chiamato newCell delle dimensioni di una struttura di cella. Non cambia i valori che erano in quella memoria - potrebbero essere qualsiasi cosa.

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);

Al fine di ottenere newCell.subcells [i], viene effettuato un calcolo per compensare dalle sottocellule di i, quindi dedotto . In particolare, ciò significa che il valore viene estratto da quell'indirizzo di memoria. Prendi, ad esempio, i == 0 ... Quindi dovremmo dereferenziare il puntatore delle sottocelle stesso (nessun offset). Poiché le sottocelle non sono definite, potrebbe essere qualsiasi cosa. Letteralmente niente! Quindi, questo richiederebbe un valore da qualche parte completamente casuale nella memoria. Non c'è garanzia di nulla con il risultato. Potrebbe stampare qualcosa, potrebbe bloccarsi. Non dovrebbe assolutamente essere fatto.

  }

  return newCell;
}

Ogni volta che lavori con un puntatore, è importante assicurarsi che sia impostato su un valore prima di dereferenziarlo. Incoraggia il tuo compilatore a darti tutti gli avvisi che può, molti compilatori moderni possono catturare questo genere di cose. Puoi anche dare ai puntatori valori predefiniti carini come 0xdeadbeef (sì! Questo è un numero in esadecimale, è anche solo una parola, quindi sembra divertente) in modo che risaltino. (L'opzione% p per printf è utile per visualizzare i puntatori, come forma grezza di debug. Anche i programmi di debugger possono mostrarli abbastanza bene.)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top