2010-08-30 12 views
8

primero (como siempre) Quiero pedir disculpas por mi inglés, puede que no sea lo suficientemente claro.Leyendo cadenas con longitud indefinida en C

No soy tan bueno en la programación C, y me pidieron que leyera una entrada de "cadena" con una longitud indefinida.

Esta es mi solución

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

char *newChar(); 
char *addChar(char *, char); 
char *readLine(void); 

int main() { 
    char *palabra; 
    palabra = newChar(); 

    palabra = readLine(); 
    printf("palabra=%s\n", palabra); 

    return 0; 
} 

char *newChar() { 
    char *list = (char *) malloc(0 * sizeof (char)); 
    *list = '\0'; 
    return list; 
} 

char *addChar(char *lst, char num) { 
    int largo = strlen(lst) + 1; 
    realloc(&lst, largo * sizeof (char)); 
    *(lst + (largo - 1)) = num; 
    *(lst + largo) = '\0'; 
    return lst; 
} 

char *readLine() { 
    char c; 
    char *palabra = newChar(); 

    c = getchar(); 
    while (c != '\n') { 
    if (c != '\n') { 
     palabra = addChar(palabra, c); 
    } 
    c = getchar(); 
    } 
    return palabra; 
} 

Por favor, le agradecería que me ayudas por decirme si es una buena idea o darme alguna otra idea (y también me dice si es una "correcta" uso para punteros).

Gracias de antemano


EDIT: Bueno, gracias por vosotros respuestas, que eran muy útiles. Ahora publico el código editado (y espero que sea mejor), tal vez podría ser útil para alguien nuevo en C (como yo) y recibir retroalimentación de nuevo.

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


void reChar(char **, int *); 
void readLine(char **, int *); 

int main() { 
    char *palabra = NULL; 
    int largo = 0; 

    reChar(&palabra, &largo); 
    readLine(&palabra, &largo); 
    printf("palabra=%s\n", palabra, largo); 

    system("pause"); 
    return 0; 
} 

void reChar(char **lst, int *largo) { 
    (*largo) += 4; 
    char *temp = (char*) realloc(*lst, (*largo) * sizeof (char)); 

    if (temp != NULL) { 
     *lst = temp; 
    } else { 
     free(*lst); 
     puts("error (re)allocating memory"); 
     exit(1); 
    } 
} 

void readLine(char **lst, int *largo) { 
    int c; 
    int pos = 0; 

    c = getchar(); 
    while (c != '\n' && c != EOF) { 
     if ((pos + 1) % 4 == 0) { 
      reChar(lst, largo); 
     } 
     (*lst)[pos] =(char) c; 
     pos++; 
     c = getchar(); 
    } 
    (*lst)[pos] = '\0'; 
} 

PS:

  • Se parece suficiente para frenar aumentar tamaño de la "palabra".

  • no estoy seguro de si la captura getchar() en un int y luego se echó en un char es la forma correcta de hadle EOF pitfall

+0

'sizeof (char)' se garantiza que siempre es 1. Esto es menor en comparación con los comentarios válidos que puede encontrar en las respuestas. –

+0

'int main()' no es una firma válida para main. Use 'int main (void)' o 'int main (int argc, char * argv [])'. – mk12

Respuesta

23
  1. buscar la definición de POSIX getline().

  2. Recuerde que debe capturar el valor de retorno desde realloc(); no se garantiza que el nuevo bloque de memoria comience en la misma posición que el anterior.

  3. Sepa que malloc(0) puede devolver un puntero nulo, o puede devolver un puntero no nulo que no se puede utilizar (porque apunta a cero bytes de memoria).

  4. No puede escribir '*list = '\0'; cuando la lista apunta a cero bytes de memoria asignada; no tienes permiso para escribir allí. Si obtienes un NULL de vuelta, es probable que obtengas un volcado de memoria. En cualquier caso, está invocando un comportamiento indefinido, que es 'A Bad Idea ™'. (Thanks)

  5. La palabra = newChar(); en main() pierde la memoria, suponiendo que solucione los otros problemas ya discutidos.

  6. El código en readLine() no considera la posibilidad de obtener EOF antes de obtener una nueva línea; eso es malo y dará como resultado un volcado del núcleo cuando falle la asignación de memoria (finalmente).

  7. Su código mostrará un rendimiento deficiente porque asigna un carácter a la vez. Normalmente, debe asignar considerablemente más de un carácter adicional a la vez; comenzar con una asignación inicial de quizás 4 bytes y duplicar la asignación cada vez que necesite más espacio podría ser mejor. Mantenga la asignación inicial pequeña para que el código de reasignación se pruebe adecuadamente.

  8. El valor de retorno de getchar() es un int, no un char. En la mayoría de las máquinas, puede devolver 256 valores de caracteres positivos diferentes (incluso si char es un tipo firmado) y un valor separado, EOF, que es distinto de todos los valores char. (. La norma permite devolver más de 256 caracteres diferentes si la máquina tiene bytes que son más grandes que 8 bits cada uno) (Thanks) El estándar C99 §7.19.7.1 dice de fgetc():

    Si final- el indicador del archivo para la corriente de entrada apuntada por la secuencia no está configurada y está presente el siguiente carácter , la función fgetc obtiene ese carácter como un carácter sin signo convertido a un int y avanza el indicador de posición del archivo asociado para el transmisión (si está definida).

    (Énfasis añadido). Se define getchar() en términos de getc(), y define getc() en términos de fgetc().

  9. (Prestado: Thanks). El primer argumento para realloc() es el puntero al inicio de la memoria asignada actualmente, no un puntero al puntero al inicio de la memoria actualmente asignada. Si no recibiste una advertencia de compilación, no estás compilando con suficientes advertencias configuradas en tu compilador. Deberías subir las advertencias al máximo.Debe prestar atención a las advertencias del compilador: normalmente son indicativas de errores en su código, especialmente cuando todavía está aprendiendo el idioma.

  10. A menudo es más fácil mantener la cuerda sin un terminador nulo hasta que sepa que ha llegado al final de la línea (o al final de la entrada). Cuando no haya más caracteres para leer (por el momento), añada el valor nulo para que la cadena finalice correctamente antes de que se devuelva. Estas funciones no necesitan que la secuencia termine correctamente mientras están leyendo, siempre y cuando se mantenga un registro de dónde se encuentra en la cadena. Sin embargo, asegúrese de tener suficiente espacio en todo momento para agregar el NUL '\0' al final de la cadena.

Ver Kernighan & Pike 'The Practice of Programming' de muchas discusiones pertinentes. También creo que Maguire 'Writing Solid Code' tiene consejos relevantes para ofrecer, para todos es algo anticuado. Sin embargo, debes tener en cuenta que hay quienes critican el libro. En consecuencia, recomiendo TPOP sobre WSC (pero Amazon tiene WSC disponible desde $ 0.01 + p & p, mientras que TPOP comienza en $ 20.00 + p & p - este puede ser el mercado que habla).


TPOP estaba previamente en http://plan9.bell-labs.com/cm/cs/tpop y http://cm.bell-labs.com/cm/cs/tpop pero ambos son ahora (10/08/2015) rota. Véase también Wikipedia en TPOP.

+0

+1 para la sugerencia de ejecución, estaba a punto de escribir el mismo –

+1

+1, pero nit menor en 8: puede devolver * al menos * 256 valores de caracteres * sin signo * diferentes ... – schot

+0

+1, pero "Si recupera un NULL, obtienes un volcado del núcleo ": escribir en NULL es exactamente tan indefinido como escribir en cualquier otro puntero no válido. No se garantiza un volcado de núcleo, incluso en sistemas POSIX (es poco probable que se mapee algo en 0 en un programa POSIX típico, pero sucede ...) – bdonlan

5
  • Siempre se están asignando un byte menos de lo que está utilizando. Por ejemplo, al principio asigna espacio para cero caracteres y luego intenta establecer el primer carácter (no existente) en '\0'.

  • realloc no toma un puntero a un puntero como primer parámetro. Se supone que debe ser utilizado de esta manera:

    lst = realloc(lst, largo * sizeof (char)); 
    
  • Si desea gestionar las condiciones de falta de memoria que tendría que comprobar si malloc() o realloc() devuelve NULL.

  • Sería más eficiente asignar un búfer más grande al principio y aumentarlo en pasos más grandes en lugar de reasignar cada carácter agregado por separado.

+1

Si 'realloc()' falla, ha filtrado la memoria que tenía anteriormente. ¡No asigne el resultado de 'realloc()' al primer argumento! –

+0

'* sizeof (char)' es redundante. – mk12

2

El primer argumento de la llamada a realloc en

realloc(&lst, largo * sizeof (char)); 

debería ser lst y no &lst

También el puntero devuelto por realloc no tiene por qué ser siempre igual a su primer argumento. Si no se encuentra memoria libre adyacente a la memoria existente, se asigna un fragmento completamente diferente y se devuelve su dirección.

char *new_lst = realloc(lst, largo * sizeof (char)); 
if(new_lst != NULL) { 
    lst = new_lst; 
} 
1

Aparte de los errores en el código, creo que es mejor crear una cadena de longitud variable en C. Una vez conseguido eso, se puede escribir una función getLine(). Esta cadena de longitud variable incluye el concepto de capacidad, por lo que su tamaño aumenta en bloques de potencias de 2, en lugar de uno por uno.

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

typedef struct _mystring { 
    char * native; 
    size_t size; 
    size_t capacity; 
} String; 

size_t String__len(String this) 
{ 
    return this.size; 
} 

String String__create(char native[], size_t capacity) { 
    String this; 

    this.size = strlen(native); 
    if (capacity < (this.size + 1)) 
     this.capacity = this.size + 1; 
    else this.capacity = capacity; 

    this.native = (char *) malloc(capacity * sizeof(char)); 
    strcpy(this.native, native); 

    return this; 
} 

String * String__set(String *this, char native[]) { 
    this->size = strlen(native); 

    if (this->size >= this->capacity) { 
     do { 
      this->capacity <<= 1; 
     } while(this->size > this->capacity); 

     this->native = realloc(this->native, this->capacity); 
    } 

    strcpy(this->native, native); 

    return this; 
} 

String * String__add(String *this, char ch) { 
    ++(this->size); 

    if (this->size >= this->capacity) { 
     do { 
      this->capacity <<= 1; 
     } while(this->size > this->capacity); 

     this->native = realloc(this->native, this->capacity); 
    } 

    char * zeroPos = this->native + (this->size -1); 
    *(zeroPos++) = ch; 
    *zeroPos = 0; 

    return this; 
} 

void String__delete(String *this) 
{ 
    free(this->native); 
} 

Una vez que haya hecho esta implementación, lo cual es útil para este problema y muchos otros, puede crear la función getLine:

String String__getLine() 
{ 
    int ch; 
    String this = String__create("", 16); 

    do { 
     ch = fgetc(stdin); 
     String__add(&this, ch); 
    } while(ch != EOF 
      && ch != '\n'); 

    size_t len = String__len(this); 
    this.size = len -1; 
    *(this.native + this.size) = 0; 

    return this; 
} 

Ahora puede simplemente usarlo:

int main() 
{ 
    printf("Enter string: "); 
    String str = String__getLine(); 
    printf("You entered: '%s'\n", str.native); 
    String__delete(&str); 

    return EXIT_SUCCESS; 
} 
+0

No se olvide de ilustrar el uso de 'String_delete()' para evitar la pérdida de memoria. –

+0

Además, tenga en cuenta que el doble guión bajo está permitido en C, pero no está permitido en C++. Aunque esta es una pregunta en C y no una pregunta en C++ (ya hay buenas soluciones disponibles en C++), no veo sentido en escribir código que no se pueda migrar fácilmente a C++. –

+0

Gracias por su advie. – Baltasarq

1

Aquí un ejemplo de trabajo para realloc y fgets. Su C89, no es necesario POSIX. Puede establecer el parámetro con su propia memoria preasignada o NULL. Una terminación "libre" siempre es necesaria.

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

char *getstringStdin(char *s) 
{ 
    char buffer[9]; 
    s=realloc(s,1); 
    *s=0; 
    while(fgets(buffer,9,stdin)) 
    { 
    s=realloc(s,strlen(s)+1+strlen(buffer)); 
    strcat(s,buffer); 
    if(strchr(s,'\n')) 
    { 
     *strchr(s,'\n')=0; 
     break; 
    } 
    } 
    return s; 
} 

main() 
{ 
    char *s; 
    while(*(s=getstringStdin(0))) /* a single Enter breaks */ 
    { 
    puts(s); 
    free(s); 
    } 
    free(s); 
    puts("end"); 
    return 0; 
} 
+0

Tiene el mismo error que en la respuesta de @ sth: si 'realloc()' falla, ha filtrado la memoria que tenía anteriormente. ¡No asigne el resultado de 'realloc()' al primer argumento! –