Problemas con strncpy y como solucionarlo.

Estoy aprendiendo C y leyendo a través de Learn C The Hard Way (ISBN-10: 0-321-88492-2). Estoy atascado en el ejercicio 17 ‘Cómo romperlo’.

Aquí está el problema del libro:

Hay un error en este progtwig porque strncpy está mal diseñado. Lea acerca de strncpy y luego intente averiguar qué sucede cuando el nombre o la dirección que da es mayor que 512 bytes. Arregle esto simplemente forzando el último carácter a ‘\ 0’ para que siempre se establezca sin importar qué (lo que debe hacer strncpy).

He leído un poco en strncpy y entiendo que es inseguro porque no agrega un byte nulo al final de la cadena. Sin embargo, no sé cómo pasar una gran cantidad de bytes a la función y no estoy seguro de cómo solucionar el problema de bytes nulos.

A continuación se muestra la función que utiliza strncpy, MAX_DATA se establece en 512.

 void Database_set(struct Connection *conn, int id, const char *name, const char *email) { struct Address *addr = &conn->db->rows[id]; if(addr->set) die("Already set, delete it first"); addr->set = 1; // WARNING: bug, read the "How To Break It" and fix this char *res = strncpy(addr->name, name, MAX_DATA); // demonstrate the strncpy bug if(!res) die("Name copy failed"); res = strncpy(addr->email, email, MAX_DATA); if(!res) die("Email copy failed"); } 

Cómo romperlo – EDITAR

A continuación se muestra un ejemplo de cómo romper strncpy:

 void Database_set(struct Connection *conn, int id, const char *name, const char *email) { struct Address *addr = &conn->db->rows[id]; if(addr->set) die("Already set, delete it first"); addr->set = 1; // WARNING: bug, read the "How To Break It" and fix this char name2[] = { 'a', 's', 't', 'r', 'i', 'n', 'g' }; char *res = strncpy(addr->name, name2, MAX_DATA); // demonstrate the strncpy bug if(!res) die("Name copy failed"); res = strncpy(addr->email, email, MAX_DATA); if(!res) die("Email copy failed"); } 

Para corregirlo, agregue un byte nulo al final de la cadena. cambiar name2 para que sea:

  char name2[] = { 'a', 's', 't', 'r', 'i', 'n', 'g', '\0' }; 

o, agregue la siguiente línea sobre la llamada a la función strncpy

 names2[sizeof(names2)-1] = '\0'; 

Otra forma de corregir el error de strncpy sería corregir la llamada a printf

 void Address_print(struct Address *addr) { printf("%d %.*s %.*s\n", addr->id, sizeof(addr->name), addr->name, sizeof(addr->email), addr->email); } 

Esto restringe a printf para generar como máximo la matriz de caracteres completa, pero no más.

¿Por qué no simplemente reemplazar strncpy con strlcpy? Según la página de manual de strlcpy:

  EXAMPLES The following sets chararray to ``abc\0\0\0'': (void)strncpy(chararray, "abc", 6); The following sets chararray to ``abcdef'' and does not NUL terminate chararray because the length of the source string is greater than or equal to the length parameter. strncpy() only NUL terminates the destination string when the length of the source string is less than the length parameter. (void)strncpy(chararray, "abcdefgh", 6); Note that strlcpy(3) is a better choice for this kind of operation. The equivalent using strlcpy(3) is simply: (void)strlcpy(buf, input, sizeof(buf)); The following copies as many characters from input to buf as will fit and NUL terminates the result. Because strncpy() does not guarantee to NUL terminate the string itself, it must be done by hand. char buf[BUFSIZ]; (void)strncpy(buf, input, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; 

Puede hacer lo siguiente, asumiendo que str1 y str2 son matrices de caracteres:

 strncpy(str1, str2, sizeof(str1) - 1); str1[sizeof(str1)-1] = '\0'; 

Esto siempre establecerá el último carácter en \0 , sin importar cuánto tiempo sea str2 . Pero tenga en cuenta que si str2 es más grande que str1 , la cadena se truncará.

La página del manual para strncpy en realidad da un código de ejemplo sobre cómo solucionar este error:

 strncpy(buf, str, n); if (n > 0) buf[n - 1]= '\0'; 

El principal problema es que addr-> name no se ha inicializado, por lo que es un puntero vacío, no apunta a ninguna parte.

Por lo tanto, antes de poder usar strncpy primero, debe asignar memoria a addr-> name, de lo contrario no funcionará.

Y como es un puntero NULO, se devuelve NULL si no lo configuró, entonces la instrucción if if será verdadera y la función die detendrá el progtwig.

Puede mirar el código fuente en el que la función Database_create no inicializa los dos punteros de cadena desde la dirección de la estructura.