2010-09-29 5 views
5

ho questo frammento di codice C:Quale di queste opzioni è una buona pratica per assegnare un valore stringa a una variabile in C?

#include<stdio.h> 
#include<stdlib.h> 
#include<string.h> 

typedef struct Date { 
    int date; 
    char* month; 
    int year; 

} Date_t; 

typedef Date_t* pDate_t; 

void assignMonth(pDate_t birth) 
{ 
    //1) 
    birth->month = "Nov"; 

    //2) 
    //birth->month = malloc(sizeof(char) * 4); 
    //birth->month = strcpy(birth->month, "Nov"); 

} 

int main() 
{ 
    Date_t birth; 
    birth.date = 13; 
    assignMonth(&birth); 
    birth.year = 1969; 


    printf("%d %s %d\n",birth.date, birth.month, birth.year); 
    return 0; 
} 

Nella funzione assignMonth ho due possibilità per l'assegnazione mese. Entrambi mi danno lo stesso risultato nell'output, quindi qual è la differenza tra loro? Penso che la seconda variante sia quella giusta, sbaglio? Se sì, perché? Se no, perché?

Grazie in anticipo per qualsiasi aiuto.

P.S. Sono interessato a ciò che sta accadendo in memoria in entrambi i casi.

risposta

6

Hai ragione, la seconda variante è quella "buona".

Ecco la differenza:

Con 1, birth->month finisce che punta alla stringa letterale "Nov". È un errore provare a modificare il contenuto di birth->month in questo caso, e quindi birth->month dovrebbe essere davvero un const char* (molti compilatori moderni avvertiranno l'assegnazione per questo motivo).

Con 2, birth->month termina puntando su un blocco di memoria assegnato il cui contenuto è "Nov". Siete quindi liberi di modificare il contenuto di birth->month e il tipo char* è preciso.L'avvertenza è che ora sei obbligato allo free(birth->month) per liberare questa memoria quando hai finito.

La ragione per cui 2 è il modo corretto di farlo in generale, anche se 1 sembra più semplice in questo caso, è che 1 in generale è fuorviante. In C, non esiste un tipo di stringa (solo sequenze di caratteri) e quindi non esiste alcuna operazione di assegnazione definita sulle stringhe. Per due char* s, s1 e s2, s1 = s2 non cambia il valore di stringa puntata da s1 essere lo stesso come s2, rende s1 punto in esattamente la stessa stringa come s2. Ciò significa che qualsiasi modifica a s1 influirà sul contenuto di s2 e viceversa. Inoltre, è necessario fare attenzione quando si disattiva quella stringa, dal momento che free(s1); free(s2); si raddoppia e causa un errore.

Detto questo, se nel programma birth->month sarà sempre solo una delle numerose stringhe costanti ("Jan", "Feb", ecc) la variante 1 è accettabile, tuttavia si dovrebbe cambiare il tipo di birth->month-const char* per chiarezza e correttezza.

+0

Per (1), "Nov" resterebbe nell'ambito?Avrei pensato che sarebbe stato aperto a essere sovrascritto da un altro metodo (sebbene questo esempio abbia solo questo metodo) poiché la funzione va fuori dal campo di applicazione (e quindi qualsiasi memoria che sta usando potrebbe essere riutilizzata). O perché questa è una stringa statica, va bene? Oppure questo compilatore dipende e diversi compilatori farebbero cose diverse? Ho sempre pensato che qualsiasi variabile o costante di qualsiasi tipo (int, pointer, char, qualunque cosa) in un metodo sarà nullo una volta che una funzione verrà chiusa, a meno che non sia stata dichiarata come 'statica'? –

+0

I valori letterali stringa non escono mai dall'ambito perché non sono memorizzati nello stack (durata archiviazione automatica). È prescritto dallo standard C che i letterali stringa abbiano una durata di memorizzazione statica, il che significa che sono essenzialmente la stessa di una costante globale 'char' array, o una costante' char' array dichiarata con 'static' all'interno di una funzione. Ciò che è definito dal compilatore è se due letterali stringa identici si riferiscono alla * stessa * stringa globale, cioè non è garantito che '" Nov "' in 'foo()' e '" Nov "' in 'bar()' lo stesso indirizzo. –

+0

Quale standard C richiede l'archiviazione statica su stringhe letterali? C89? –

0

Nel primo caso non è possibile fare qualcosa come birth->month[i]= 'c'. In altre parole, non è possibile modificare la stringa letterale "Mov" puntata da birth->month perché è memorizzata nella sezione di sola lettura della memoria.

Nel secondo caso è possibile modificare il contenuto di p->month perché "Mov" si trova nell'heap. Anche in questo caso è necessario deallocare la memoria allocata utilizzando free.

9

Dipende da cosa si desidera fare con birth.month in seguito. Se non hai intenzione di cambiarlo, allora il primo è migliore (più veloce, non è richiesto alcun requisito di pulizia della memoria e ogni oggetto Date_t condivide gli stessi dati). Ma se questo è il caso, cambierei la definizione di month a const char *. In effetti, qualsiasi tentativo di scrivere su *birth.month causerà un comportamento indefinito.

Il secondo approccio causerà una perdita di memoria a meno che non si ricordi di free(birth.month) prima che birth non rientri nello scope.

+1

@ sje397: No. 'char * const' dichiara un puntatore immutabile. 'const char *' dichiara un puntatore a un oggetto immutabile. –

+0

si scusa ... corto circuito mentale. – sje397

1

Con l'opzione 1 non si alloca mai memoria per memorizzare "Nov", che va bene perché è una stringa statica. Una quantità fissa di memoria è stata allocata automaticamente. Questo andrà bene fintanto che si tratta di una stringa che appare letteralmente nella fonte e non si tenta mai di modificarla. Se si desidera leggere un valore dall'utente o da un file, è necessario prima allocarlo.

0

Seperare alla domanda; perchè struct Date typedefed? ha già un tipo - "struct Date".

È possibile utilizzare un tipo incompleto se si desidera nascondere la declinazione della struttura.

In base alla mia esperienza, le persone sembrano typedef perché pensano che dovrebbero - senza effettivamente pensare all'effetto di farlo.

+2

Il motivo per le strutture typedef è lo stesso di qualsiasi typedef; per salvare la quantità di battitura (nessun gioco di parole previsto) richiesto e per fornire un livello di astrazione (a livello puramente sintetico). Ho sentito l'argomentazione che è una "cattiva pratica" nascondere una struttura dietro un typedef, ma non ho mai sentito una buona giustificazione per questo. –

+0

Risparmia sulla digitazione? senza offesa, ma penso che sia ridicolo. Si tratta di salvare la parola "struct". In questo caso, perché non utilizzare #defines per abbreviare le parole chiave lunghe in C? –

+0

L'argomento dell'astrazione è quello che penso sia generalmente la ragione fornita per l'uso di un typedef. Penso che l'astrazione non sia quasi mai onorata, perché se è richiesta l'astrazione, allora il tipo sottostante * non può * essere conosciuto. Ciò significa che non è possibile, ad esempio, utilizzare alcun operatore di confronto, poiché è necessario assumere un tipo che può essere confrontato. In quanto tale, TUTTE le operazioni su un typedef devono avvenire attraverso le funzioni. I * MAI * non capisco; e se questo non è fatto, allora typedef è dannoso, perché oscura il tipo, * mentre ancora richiede al lettore di conoscere il tipo *. –

2

suggerisco uno:

const char* month; 
... 
birth->month = "Nov"; 

o:

char month[4]; 
... 
strcpy(birth->month, "Nov"); 

evitando l'allocazione di memoria del tutto.

0

Per questo esempio, non importa troppo. Se hai un sacco di variabili Date_t (ad esempio, un database in memoria), il primo methow porterà a un utilizzo minore della memoria nel suo complesso, con il trucco che non dovresti, in nessuna circostanza, cambiare alcuno dei caratteri nelle stringhe, poiché tutte le stringhe "Nov" sarebbero "la stessa stringa" (un puntatore agli stessi 4 caratteri).

Quindi, in una certa misura, entrambe le varianti sono buone, ma la migliore dipende dai modelli di utilizzo previsti.

3

Né è corretto. A tutti manca il fatto che questa struttura sia intrinsecamente infranta. Il mese deve essere un numero intero compreso tra 1 e 12, utilizzato come indice in una serie di stringhe static const quando è necessario stampare il mese come stringa.

+0

Quindi, 'month' dovrebbe essere dichiarato come' enum' invece, per fornire un migliore controllo sulla convalida dei dati. – Hemant

+0

Sono d'accordo, ma "rotto" è forse troppo forte. Forse mal progettato e mal concepito. – Clifford

Problemi correlati