¿Cómo pasar una referencia a una cadena?

Todo lo que he leído sobre scanf , fgets y fgets es que son problemáticos; Ya sea con espacio en blanco, desbordamiento o complejidad. Estoy tomando un curso de introducción a C y, como tengo suficiente experiencia en progtwigción en Java y otros lenguajes para sentirme seguro al hacerlo, decidí crear mi propia función para obtener una entrada de cadena del usuario mediante la función getchar() . Las piezas relevantes de mi código están abajo:

 bool get_string (char prompt[], char* string) { printf(prompt); // Prompt the user // Ensure string is allocated to store the null character and reset it if it has been initialized do { string = (char*)malloc(sizeof(char)); } while (string == NULL); int index = 0; char place = getchar(); while (place != '\n') // Obtain all characters before endl { string = (char*)realloc(string, sizeof(string) + sizeof(char)); // Create room in the array if (string == NULL) return false; // Ensure realloc worked correctly string[index++] = place; // Place the new string in the second to last index place = getchar(); } string[index] = '\0'; // Append the null character at the end return true; // Operation succeeded } 

A través de pruebas y depuración, me las arreglé para descubrir que:

  1. Mi función cumple con la especificación localmente, y la string parámetros contiene la cadena ingresada.
  2. El puntero char* que estoy usando en mi método principal no se está modificando. Después de llamar a mi función de entrada, la desreferencia de este puntero permanece igual que su valor inicial.

Tenía la impresión de que, como pasaba un puntero a la función, trataría el parámetro como referencia. De hecho, esto es lo que me enseñaron en la clase. Cualquier percepción puede ayudar.

Puntos de bonificación otorgados si:

Puedes decirme por qué no me permite liberar el puntero de mi char* en main. (Tal vez porque no se ha asignado a través del mismo problema?)

¿Qué más estoy haciendo mal, como llamar a realloc demasiadas veces?

NOTA: Estoy utilizando el comstackdor MSVC C89 y defino precomstackción bool, true y false.

El puntero char * que estoy usando en mi método principal no se está modificando. Después de llamar a mi función de entrada, la desreferencia de este puntero permanece igual que su valor inicial.

Esto es algo que sorprende a mucha gente cuando comienzan a escribir C. Si desea que una función actualice un parámetro que es un valor de puntero , debe pasar un puntero a un puntero.

Supongamos lo siguiente:

 void foo( T *p ) { *p = new_value(); // update the thing p is pointing to } void bar( void ) { T val; foo( &val ); // update val } 

Bastante sencillo: queremos que la función foo escriba un nuevo valor en val , así que pasamos un puntero a val . Ahora reemplace el tipo T con el tipo R * :

 void foo( R **p ) { *p = new_value(); // update the thing p is pointing to } void bar( void ) { R *val; foo( &val ); // update val } 

La semántica es exactamente la misma: estamos escribiendo un nuevo valor para val . Todo lo que ha cambiado es los tipos de val y p .

Por lo tanto, su prototipo de función debe ser

 bool get_string (char prompt[], char **string) 

ya que desea modificar el valor del puntero al que apunta la string . Esto también significa que en el cuerpo de su función, está escribiendo en *string , no en string .

El método preferido para escribir una llamada malloc es

 T *p = malloc( sizeof *p * number_of_elements ); 

o

 T *p; ... p = malloc( sizeof *p * number of elements ); 

El lanzamiento no es necesario a partir de C89 1 , y bajo C89 puede suprimir un diagnóstico útil. Ya que C99 int declaraciones int implícitas ya no es un problema, pero es mejor dejarlo. Observe también el operando de sizeof ; en lugar de una expresión de tipo como (char) , usamos la expresión *p . Como el tipo de expresión *p es T , entonces sizeof *p da el mismo resultado que sizeof (T) . No solo se ve más limpio, sino que también reduce el mantenimiento si alguna vez decide cambiar el tipo de p .

En tu caso, p es *string , dándonos

 *string = malloc( sizeof **string ); 

Dado que realloc es una operación potencialmente costosa, realmente no desea llamarlo para cada nuevo personaje. Una mejor estrategia es asignar inicialmente un búfer que debería manejar la mayoría de los casos, luego extenderlo por algún factor del tamaño actual (como duplicarlo) según sea necesario. En este caso, haría algo como lo siguiente:

 size_t stringSize = INITIAL_SIZE; // keeps track of the physical buffer size *string = malloc( sizeof *string * stringSize ); if ( ! *string ) // initial memory allocation failed, panic while ((place = getchar()) != '\n' && place != EOF) { if ( index == stringSize ) { // double the buffer size char *tmp = realloc( *string, sizeof **string * ( stringSize * 2 ) ); if ( tmp ) { *string = tmp; stringSize *= 2; } } (*string)[index++] = place; } 

Esto reduce el número total de llamadas a realloc , lo que debería maximizar su rendimiento.

Además, si realloc falla, devolverá NULL y dejará el búfer asignado actualmente en su lugar; sin embargo, si eso sucede, no querrá volver a asignar ese resultado a la *string , de lo contrario perderá su única referencia a esa memoria. Siempre debe asignar el resultado de realloc a una variable temporal y verificarlo antes de volver a asignar la *string .

También tenga en cuenta cómo subíndimos *string ; ya que el operador del subíndice [] tiene una prioridad más alta que el operador unario * , *string[index++] se analizará como *(string[index++] ), que no es lo que queremos, queremos indexar en *string , no en string . Entonces, tenemos que agrupar explícitamente el operador * usando paréntesis, dándonos

 (*string)[index++] = place; 

1. Sin embargo, es necesario en C ++, pero si estás escribiendo C ++, deberías usar el new operador.

Tenía la impresión de que, como pasaba un puntero a la función, trataría el parámetro como referencia. De hecho, esto es lo que me enseñaron en la clase. Cualquier percepción puede ayudar.

No hay referencias por referencia en C. Cada parámetro que pasa es por valor .

Así:

 void foo(int a) { a = 21; // a == 21 for the rest of THIS function } void bar(void) { int x = 42; foo(x); // x == 42 } 

Lo mismo ocurre con:

 static int john = 21; static int harry = 42; void foo(int * a) { a = &john; // a points to john for the rest of THIS function } void bar(void) { int * x = &harry; foo(x); // x still points to harry } 

Si desea cambiar un puntero a través de un parámetro, deberá pasar un puntero a ese puntero:

 static int john = 21; static int harry = 42; void foo(int ** m) { *m = &john; } void bar(void) { int * x = &harry; foo(&x); // passing the address of x by value // x now points to john } 

¿Qué más estoy haciendo mal, como llamar a realloc demasiadas veces?

 printf(prompt); 

Problema de seguridad: intente cosas como "%s" como valor para el prompt . Se usa mejor puts o printf("%s", prompt) .

 do { string = (char*)malloc(sizeof(char)); } while (string == NULL); 

Ese es un posible bucle infinito. Si malloc falla, volver a llamar inmediatamente no cambiará nada. También: No lance el valor de retorno de malloc . Además, se define sizeof(char) como igual a 1 .

 int index = 0; 

Para los índices use size_t .

 char place = getchar(); 

Hay una razón por la que getchar devuelve un int , es decir, para poder verificar el EOF , que usted …

 while (place != '\n') 

… no lo hagas, pero deberías!

 string = (char*)realloc(string, sizeof(string) + sizeof(char)); 

No emita el valor de retorno, sizeof(string) no está haciendo lo que cree que hace, es una constante de tiempo de comstackción (probablemente 8 en un sistema de 64 bits).

 if (string == NULL) return false; 

Pérdida de memoria, porque …

Si no hay suficiente memoria, el bloque de memoria anterior no se libera y se devuelve el puntero nulo.

[Fuente]


Así es como leí una línea en C:

 #include  #include  #include  #include  char * readline(char const * const prompt) { char buffer[10]; char * string = malloc(1); if (string == NULL) { return NULL; } // The accumulated length of the already read input string. // This could be computed using strlen, but remembering it // in a separate variable is better, performancewise. size_t accumLength = 0; string[0] = '\0'; printf("%s", prompt); while (fgets(buffer, 10, stdin) != NULL) { // To see what has been read in this iteration: // printf("READ: |%s|\n", buffer); // Compute the length of the new chunk that has been read: size_t const newChunkLength = strlen(buffer); // Try to enlarge the string so that the new chunk can be appended: char * const newString = realloc(string, accumLength + newChunkLength + 1); if (newString == NULL) { free(string); return NULL; } string = newString; // Append the new chunk: strcpy(string + accumLength, buffer); accumLength += newChunkLength; // Done if the last character was a newline character assert(accumLength > 0); if (string[accumLength - 1] == '\n') { // NOTE: Wasting 1 char, possible solution: realloc. string[accumLength - 1] = '\0'; return string; } } // EOF is not an error! if (feof(stdin)) { return string; } free(string); return NULL; } int main(int argc, char ** argv) { char const * const input = readline(">"); printf("---\n%s\n---\n", input); return 0; } 

Un OP quiere “pasar una referencia a una cadena”, luego pasar a la función la dirección donde se almacena la dirección del primer elemento de la cadena.

 // The function signature needs some changes // 1: string read. 0: out-of-memory EOF:end-of-file // const * int get_string(const char prompt[], char** string) { // Never do this. If prompt contain `'%'`, code becomes a hackers target // printf(prompt); // Prompt the user fputs(prompt, stdout); // Prompt the user fflush(stdout); // use size_t, not int // int index size_t size = 0; // Keep track of size for (;;) { // Use int to distinguish EOF from all other char // char place = getchar(); int place = getchar(); // Note: reallocating every loop is generally inefficient void *former = *string; // sizeof(string) + sizeof(char) // sizeof(string) is the size of the pointer, not the size of memory it points to. // sizeof(char) is always 1 *string = realloc(*string, size + 1); if (*string == NULL) { free(former); // free old buffer return 0; // fail } // add termination on \n or EOF if (place == '\n' || place == EOF) { // Add detection and housekeeping for EOF if (place == EOF && size == 0) { free(*string); *string = NULL; return EOF; } break; } (*string)[size++] = place; } (*string)[size] = `\0`; return 1; // Operation succeeded } 

uso

 char *s = NULL; while (get_string("Hello ", &s) > 0) { puts(s); } free(s); s = NULL;