2012-04-06 13 views
11

Soy un estudiante aprendiendo C++, y estoy tratando de entender cómo funcionan las matrices de caracteres terminados en nulo. Supongamos que definir una matriz de caracteres así:C++ char matriz nulo ubicación del terminador

char* str1 = "hello world"; 

Como se esperaba, strlen(str1) es igual a 11, y es terminada en nulo.

¿Dónde pone C++ el terminador nulo, si los 11 elementos de la matriz de caracteres anterior están llenos con los caracteres "mundo hello"? ¿En realidad está asignando una matriz de longitud 12 en lugar de 11, siendo el 12º carácter '\0'? CPlusPlus.com parece sugerir que uno de los 11 tendría que ser '\0', a menos que esté hecho asignando 12.

Supongamos que yo haga lo siguiente:

// Create a new char array 
char* str2 = (char*) malloc(strlen(str1)); 

// Copy the first one to the second one 
strncpy(str2, str1, strlen(str1)); 

// Output the second one 
cout << "Str2: " << str2 << endl; 

Esto da salida a Str2: hello worldatcomY╗°g♠↕, que supongo es C++ leer el memoria en el lugar señalado por el puntero char* str2 hasta que encuentre lo que interpreta como un carácter nulo.

Sin embargo, si a continuación, hago esto:

// Null-terminate the second one 
str2[strlen(str1)] = '\0'; 

// Output the second one again 
cout << "Terminated Str2: " << str2 << endl; 

Genera Terminated Str2: hello world como se esperaba.

Pero no escrito a str2[11] implica que estamos escribiendo fuera del espacio de memoria asignada de str2, ya str2[11] es el byte 12, pero sólo se asignaron 11 bytes?

La ejecución de este código no parece causar advertencias del compilador o errores de tiempo de ejecución. ¿Es seguro hacerlo en la práctica? ¿Sería mejor usar malloc(strlen(str1) + 1) en lugar de malloc(strlen(str1))?

+1

No, uno de los 11 caracteres es de hecho '\ 0' ... es broma :-) – hirschhornsalz

+1

Como está aprendiendo, es importante que sepa desde el principio los nombres de las cosas que está aprendiendo. La expresión 'char * str1 =" hello world ";' no define una matriz de caracteres, sino un * puntero * a un literal (incidentalmente la conversión de 'const char *' a 'char *' está en desuso, por lo que el compilador debería te lo he advertido). El * literal * en sí mismo es una * matriz * de * constante * caracteres con un terminador nulo, pero la variable que ha definido es un * puntero *. –

Respuesta

11

En el caso de una cadena literal, el compilador en realidad está reservando un elemento adicional char para el elemento \0.

// Create a new char array 
char* str2 = (char*) malloc(strlen(str1)); 

Este es un error común que hacen los nuevos programadores de C. Al asignar el almacenamiento para un char*, debe asignar el número de caracteres + 1 más para almacenar el \0. No asignar el almacenamiento extra aquí significa esta línea también es ilegal

// Null-terminate the second one 
str2[strlen(str1)] = '\0'; 

Aquí en realidad estás escribiendo más allá del final de la memoria que asigna. Al asignar elementos X, el último byte legal al que puede acceder es la dirección de la memoria compensada por X - 1. Escribir en el elemento X provoca un comportamiento indefinido. A menudo funcionará pero es una bomba de relojería.

La forma correcta de escribir esto es la siguiente

size_t size = strlen(str1) + sizeof(char); 
char* str2 = (char*) malloc(size); 
strncpy(str2, str1, size); 

// Output the second one 
cout << "Str2: " << str2 << endl; 

En este ejemplo, el str2[size - 1] = '\0' no es realmente necesario. La función strncpy llenará todos los espacios adicionales con el terminador nulo. Aquí sólo hay size - 1 elementos en str1 por lo que el elemento final de la matriz es que no sean necesarios y se llenarán de \0

+0

¿Cuál es el propósito de definir explícitamente 'size_t size = strlen (str1) + sizeof (char);' en su ejemplo? ¿Estaría bien usar 'malloc (strlen (str1) +1)', ya que sabemos que un char es de 1 byte? –

+1

@JohnMahoney hay dos razones por las que utilicé el 'tamaño' local. El primero es el rendimiento. La función 'strlen' aunque no es costosa es O (N) y dado que la cadena no cambia, no hay razón para ejecutarla varias veces. La porción '+ sizeof (char)' es principalmente estilo. Un '+ 1' hace lo mismo, prefiero la notación' sizeof (char) 'más explícita – JaredPar

+1

Mejor:' char * str2 = malloc (str1) + 1); if (str2 == NULL) {/ * handle allocation failure * /} strcpy (str2, str1); '' sizeof (char) es 1 por definición. 'strncpy' funciona en este caso, pero * no * es simplemente una versión" más segura "de' strcpy'. –

6

¿En realidad está asignando una matriz de longitud 12 en lugar de 11, siendo el 12º carácter '\ 0'?

Sí.

Pero no escrito a str2[11] implica que estamos escribiendo fuera del espacio de memoria asignada de str2, ya str2[11] es el byte 12, pero sólo se asignaron 11 bytes?

Sí.

¿Sería mejor usar malloc(strlen(str1) + 1) en lugar de malloc(strlen(str1))?

Sí, porque la segunda forma no es lo suficientemente larga para copiar la cadena.

La ejecución de este código no parece causar advertencias del compilador o errores de tiempo de ejecución.

Detectar esto en todos los casos menos en los más simples es un problema muy difícil. Entonces, los autores del compilador simplemente no se molestan.


Este tipo de complejidad es exactamente por qué usted debe utilizar std::string en lugar de cadenas estilo C primas si está escribiendo C++. Es tan simple como esto:

std::string str1 = "hello world"; 
std::string str2 = str1; 
1

El literal "hello world" es una matriz char que se parece a:

{ 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' } 

Así que, sí, el literal es 12 char s de tamaño.

Además, malloc(strlen(str1)) está asignando memoria para 1 byte menos de lo necesario, ya que strlen devuelve la longitud de la cadena, sin incluir el terminador NUL. Escribir en str[strlen(str1)] es escribir 1 byte más allá de la cantidad de memoria que ha asignado.

Su compilador no le dirá eso, pero si ejecuta su programa a través de valgrind o un programa similar disponible en su sistema, le dirá si no está accediendo a la memoria.

1

Para una cadena C estándar, la longitud de la matriz que está almacenando la cadena es siempre un carácter ya continuación la longitud de la cadena en caracteres. Por lo tanto, su cadena "hello world" tiene una longitud de cadena de 11, pero requiere una matriz de respaldo con 12 entradas.

La razón de esto es simplemente la forma en que se leen esas cadenas. Las funciones que manejan esas cadenas básicamente leen los caracteres de la cadena uno por uno hasta que encuentran el carácter de terminación '\0' y se detienen en este punto. Si a este personaje le faltan esas funciones simplemente siga leyendo la memoria hasta que llegue a un área de memoria protegida que haga que el sistema operativo host mate su aplicación o hasta que encuentre el carácter de terminación.

Además, si inicializa una matriz de caracteres con la longitud 11 y escribe la cadena "hello world" en ella producirá problemas masivos. Debido a que se espera que la matriz contenga al menos 12 caracteres. Eso significa que el byte que sigue a la matriz en la memoria se sobrescribe. Resultando en efectos secundarios impredecibles.

Además, mientras trabaja con C++, es posible que desee consultar std:string. Esta clase es accesible si está usando C++ y proporciona un mejor manejo de las cadenas. Vale la pena investigar eso.

2

Creo que está confundido por el valor de retorno de strlen. Devuelve la longitud de la cadena, y no debe confundirse con el tamaño de la matriz que contiene la cadena. Considere este ejemplo:

char* str = "Hello\0 world"; 

Agregué un carácter nulo en el medio de la cadena, que es perfectamente válido. Aquí la matriz tendrá una longitud de 13 (12 caracteres + el carácter nulo final), pero strlen(str) devolverá 5, porque hay 5 caracteres antes del primer carácter nulo. strlen simplemente cuenta los caracteres hasta que se encuentra un carácter nulo.

Así que si uso el código:

char* str1 = "Hello\0 world"; 
char* str2 = (char*) malloc(strlen(str1)); // strlen(str1) will return 5 
strncpy(str2, str1, strlen(str1)); 
cout << "Str2: " << str2 << endl; 

La matriz str2 tendrá una longitud de 5, y no será terminada por un carácter nulo (porque strlen no cuenta él). ¿Es esto lo que esperabas?

+0

[Pregunta similar] (https://stackoverflow.com/questions/10050228/c-char-array-null-terminator-location) – user3583535

0

Creo que lo que necesita saber es que las matrices de caracteres comienzan desde 0 y van hasta la longitud de la matriz-1 y la longitud de la matriz de posición tiene el terminador ('\ 0').
En su caso:

str1[0] == 'h'; 
str1[10] == 'd'; 
str1[11] == '\0'; 

Por esta razón, es str2 correcta [strlen (cadena1)] = '\ 0';
El problema con la salida después de strncpy es porque copia 11 elementos (0..10) por lo que debe poner manualmente el terminador (str2 [11] = '\ 0').

Cuestiones relacionadas