2010-09-29 18 views
5

tengo este fragmento de código C:¿Cuál de estas opciones es una buena práctica para asignar un valor de cadena a una variable en 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; 
} 

En la función assignMonth Tengo dos posibilidades para la asignación de mes. Ambos me dan el mismo resultado en la salida, entonces, ¿cuál es la diferencia entre ellos? Creo que la segunda variante es la buena, ¿me equivoco? ¿Si es así por qué? Si no, ¿por qué?

Gracias de antemano por cualquier ayuda.

P.S. Me interesa lo que está sucediendo en la memoria en ambos casos.

Respuesta

6

Tiene razón, la segunda variante es la "buena".

Aquí está la diferencia:

con 1, birth->month termina apuntando a la cadena literal "Nov". Es un error intentar modificar los contenidos de birth->month en este caso, y así birth->month debería ser realmente un const char* (muchos compiladores modernos advertirán sobre la asignación por este motivo).

Con 2, birth->month termina apuntando a un bloque asignado de memoria cuyos contenidos son "Nov". Puede modificar los contenidos de birth->month y el tipo char* es correcto.La advertencia es que ahora también se requiere free(birth->month) para liberar esta memoria cuando haya terminado con ella.

La razón por la que 2 es la forma correcta de hacerlo en general, aunque 1 parece más simple en este caso, es que 1 en general es engañoso. En C, no hay ningún tipo de cadena (solo secuencias de caracteres), por lo que no hay una operación de asignación definida en las cadenas. Durante dos char* s, s1 y s2, s1 = s2 no cambia el valor de la cadena a la que apunta s1 a ser el mismo que s2, hace s1 punto en exactamente la misma cadena comos2. Esto significa que cualquier cambio a s1 afectará los contenidos de s2, y viceversa. Además, ahora debe tener cuidado al desasignar esa cadena, ya que free(s1); free(s2); se duplicará y provocará un error.

Dicho esto, si en su programa birth->month sólo habrá siempre una de las varias cadenas constantes ("Jan", "Feb", etc.) la variante 1 es aceptable, sin embargo, debe cambiar el tipo de birth->month-const char* para mayor claridad y exactitud.

+0

Para (1), ¿estaría "Nov" dentro del alcance?Hubiera pensado que esto podría ser sobreescrito por otro método (aunque este ejemplo solo tiene este método) ya que la función queda fuera del alcance (y, por lo tanto, cualquier memoria que esté utilizando podría reutilizarse). O porque esta es una cadena estática, ¿está bien? ¿O es este compilador dependiente, y diferentes compiladores harían cosas diferentes? Siempre he supuesto que cualquier variable o constante de cualquier tipo (int, puntero, char, lo que sea) en un método será nula una vez que una función finalice, a menos que haya sido declarada como "estática". –

+0

Los literales de cadena nunca salen del alcance porque no se almacenan en la pila (duración de almacenamiento automático). El estándar C exige que los literales de cadena tengan una duración de almacenamiento estática, lo que significa que son esencialmente lo mismo que una matriz 'char' constante global, o una matriz' char' constante declarada con 'static' dentro de una función. Lo que está definido por el compilador es si dos literales de cadena idénticos se refieren a la cadena * same * global, es decir, no se garantiza que '" Nov "' en 'foo()' y '" Nov "' en 'bar()' la misma dirección –

+0

¿Qué estándar de C ordena el almacenamiento estático en literales de cadena? C89? –

0

En el primer caso, no puede hacer algo como birth->month[i]= 'c'. En otras palabras, no puede modificar el literal de cadena "Mov" señalado por birth->month porque está almacenado en la sección de solo lectura de la memoria.

En el segundo caso, puede modificar el contenido de p->month porque "Mov" reside en el montón. También necesita desasignar la memoria asignada usando free en este caso.

9

Depende de lo que quieras hacer con birth.month después. Si no tiene la intención de cambiarlo, entonces el primero es mejor (más rápido, no se requiere requisito de limpieza de memoria, y cada objeto Date_t comparte los mismos datos). Pero si ese es el caso, cambiaría la definición de month a const char *. De hecho, cualquier intento de escribir en *birth.month causará un comportamiento indefinido.

El segundo enfoque provocará una pérdida de memoria a menos que recuerde free(birth.month) antes de que birth salga del alcance.

+1

@ sje397: No. 'char * const' declara un puntero inmutable. 'const char *' declara un puntero a un objeto inmutable. –

+0

sí, lo siento ... cortocircuito mental. – sje397

1

Con la opción 1, nunca asigna memoria para almacenar "Nov", lo cual está bien porque es una cadena estática. Se asignó una cantidad fija de memoria automáticamente. Esto estará bien siempre que sea una cadena que aparezca literalmente en la fuente y nunca intentes modificarla. Si quería leer un valor del usuario o de un archivo, primero debe asignarlo.

0

Separate de tu pregunta; ¿Por qué struct date typedefed? ya tiene un tipo - "struct Date".

Puede utilizar un tipo incompleto si desea ocultar la declinación de la estructura.

En mi experiencia, las personas parecen defraudarse porque creen que deberían hacerlo, sin pensar realmente en el efecto de hacerlo.

+2

El motivo de las estructuras de deflexión de tipo es el mismo que para cualquier tipodef; para guardar la cantidad de tipeo (sin juego de palabras) requerido, y para proporcionar un nivel de abstracción (en un nivel puramente sintáctico). He escuchado el argumento de que es una "mala práctica" esconder una estructura detrás de un typedef, pero nunca escuché una buena justificación para eso. –

+0

¿Guardar en tipeo? sin ofender, pero creo que es ridículo. Esto equivale a guardar la palabra 'struct'. Si ese es el caso, ¿por qué no utilizar #defines para acortar palabras clave largas en C? –

+0

El argumento de la abstracción es el que, en general, creo que es el motivo que se da para el uso de un typedef. Creo que la abstracción, sin embargo, casi no se respeta, ya que si se requiere abstracción, entonces el tipo subyacente * no puede * ser conocido. Esto significa que no puede, por ejemplo, usar ningún operador de comparación, porque hacerlo es suponer un tipo que se puede comparar. Como tal, TODAS las operaciones en un typedef deben ocurrir a través de funciones. I * NUNCA * veo que esto ocurra; y si esto no se hace, entonces el typedef es dañino, ya que oscurece el tipo, * al mismo tiempo que requiere que el lector conozca el tipo *. –

2

que sugieren ya sea:

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

o:

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

evitando la asignación de memoria por completo.

0

Para este ejemplo, no importa demasiado. Si tiene muchas variables Date_t (en, por ejemplo, una base de datos en memoria), la primera methow dará lugar a un menor uso de memoria en general, con la idea de que no debe, bajo ninguna circunstancia, cambiar ninguno de los caracteres en las cadenas, ya que todas las cadenas "Nov" serían "la misma" cadena (un puntero a los mismos 4 caracteres).

Por lo tanto, hasta cierto punto, ambas variantes son buenas, pero la mejor dependerá de los patrones de uso esperados.

3

Ninguno es correcto. Todos se están perdiendo el hecho de que esta estructura está inherentemente rota. El mes debe ser un número entero que va del 1 al 12, utilizado como índice en una matriz de cadenas static const cuando necesita imprimir el mes como una cadena.

+0

Luego, 'month' se debe declarar como 'enum' en su lugar, para proporcionar un mejor control sobre la validación de datos. – Hemant

+0

Estoy de acuerdo, pero 'roto' es quizás demasiado fuerte. Mal diseñado y mal pensado tal vez. – Clifford

Cuestiones relacionadas