2010-04-15 18 views
5

Tengo un programa en C que usa Solaris con una compatibilidad muy antigua, parece. Muchos ejemplos, incluso aquí en SO, no funcionan, así como muchos códigos que he escrito en Mac OS X.¿Cuál es la forma más segura de pasar cuerdas en C?

Entonces, cuando se usa una C muy estricta, ¿cuál es la forma más segura de pasar cadenas?

Actualmente estoy usando punteros de char por todo el lugar, debido a lo que pensé que era la simplicidad. Así que tengo funciones que devuelven char *, les estoy pasando char *, etc.

Ya estoy viendo un comportamiento extraño, como un char * Pasé su valor correcto cuando entro en una función, y luego el valor se ha ido misteriosamente O se ha corrompido/sobrescrito después de algo simple como un printf() o un malloc a algún otro puntero.

Una aproximación a las funciones, que estoy seguro que es incorrecta, podrían ser:

... Esto parece descuidado. ¿Alguien puede señalarme en la dirección correcta con un simple requerimiento?

actualización

Un ejemplo de una función donde estoy en una pérdida de lo que está sucediendo. No estoy seguro si esto es suficiente para averiguarlo, pero aquí va:'

char *get_fullpath(char *command, char *paths) { 
    printf("paths inside function %s\n", paths); // Prints value of paths just fine 

    char *fullpath = malloc(MAX_STRLENGTH*sizeof(char*)); 

    printf("paths after malloc %s\n", paths); // paths is all of a sudden just blank 
} 
+0

Creo que, más o menos, estás haciendo algo que invoca un comportamiento indefinido. Antes de echarle la culpa al compilador o al sistema operativo, sugiero que compartas algún código de ejemplo con nosotros, para que podamos decirte si tu código original que funcionó en OS X es realmente válido. –

+2

Eso solo se ve ... mal al menos. ¿Asignas a la matriz (???) ... a la que quieres aplicar strcpy? Usted tiene un returntr pero devuelve localstr (en la pila, ¡vaya!), Etc. De todos modos, bienvenido al divertido mundo de C. La propiedad de los objetos (sí, C también los tiene) debe estar claramente definida. Por ejemplo, ¿qué ocurre si el código anterior se invoca como myfunction ("¡Hola mundo!") - de todos modos, define los contratos. Un enfoque es hacer que el CALLER sea responsable de pasar un objeto válido capaz de tomar n caracteres (si se requieren más, la llamada fallará, etc.) –

+0

No entiendo qué significa "C realmente estricta". Estoy de acuerdo con Michael en que el "comportamiento realmente extraño" que estás viendo es simplemente un comportamiento indefinido dado el código anterior. No hay una forma especial de pasar una "cuerda" en C, funciona igual que cualquier otra matriz. ¿Qué es exactamente lo que estás teniendo un problema? –

Respuesta

12

código C bien escrito se adhiere a la convención siguiente:

  • todas las funciones devuelven un código de estado de tipo int , donde un valor de retorno de 0 indica el éxito, y un -1 indica un fallo. En caso de falla, la función debe establecer errno con un valor apropiado (por ejemplo, EINVAL).
  • Los valores que son "informados" por una función se deben informar mediante el uso de "parámetros de salida". En otras palabras, uno de los parámetros debe ser un puntero al objeto de destino.
  • La propiedad de los punteros debe pertenecer al invocador; por lo tanto, una función no debe free alguno de sus parámetros, y solo debe free objetos que él mismo asigna con malloc/calloc.
  • Las cadenas se deben pasar como objetos const char* o como objetos char*, dependiendo de si la cadena se va a sobrescribir. Si la cadena no se va a modificar, se debe usar const char*.
  • Cada vez que se pasa una matriz que no es una cadena terminada en NUL, se debe proporcionar un parámetro que indique el número de elementos en la matriz o la capacidad de esa matriz.
  • Cuando se pasa un objeto string/buffer modificable (es decir, char*) a una función, y esa función es sobrescribir, anexar o modificar la cadena, se debe proporcionar un parámetro que indique la capacidad del string/buffer (para permitir tamaños de búfer dinámicos y evitar el desbordamiento de bufffer).

Debo señalar que en el código de ejemplo, va a devolver localstr y no returnstr. En consecuencia, está devolviendo una dirección de un objeto en el marco de pila de la función actual. El marco de pila de la función actual desaparecerá una vez que la función haya regresado. Invocar otra función inmediatamente después probablemente alterará los datos en esa ubicación, lo que provocará la corrupción que ha observado. Devolver la dirección de una variable local conduce a un "comportamiento indefinido" y es incorrecta.

Editar
Sobre la base de su código actualizado (get_fullpath), está claro que el problema no está en su get_fullpath función, sino más bien en la función que está llamando. Lo más probable es que la variable paths esté siendo suministrada por una función que devuelve la dirección de una variable local. En consecuencia, cuando crea una variable local dentro de get_fullpath, utiliza la misma ubicación exacta en la pila que las rutas previamente ocupadas. Como "paths" es aliasing "fullpaths", básicamente se sobrescribe con la dirección del búfer que has malloced, que está en blanco.

Editar 2
He creado una página en C Coding Conventionsmy website con las recomendaciones más detalladas, explicaciones y ejemplos para escribir código C, en caso de estar interesado. Además, la afirmación de que se devuelve localstr en lugar de returnstr ya no es válida desde la última edición de la pregunta.

+1

Bien, me gusta esta lista. ¡Gracias por la ayuda! Tengo mucho que aprender sobre ser disciplinado ... – chucknelson

+1

+1 para su tercer punto! – JustJeff

+1

Ahí es donde me pierdo, ¿cómo es que "fullpaths" tocan la memoria que ya está asignada y siendo utilizada en "rutas"? – chucknelson

4

No se puede devolver un puntero a una matriz que está asignado localmente dentro de la función. Tan pronto como la función retorne, esa matriz será destruida.

Además, cuando se pone

char localstr[MAX_STRLENGTH] = strcpy(localstr, somestr); 

lo que sucede es que strcpy() copia los bytes en el array localstr [], pero entonces usted tiene una cosa asignación innecesaria pasando. Probablemente se podría obtener el efecto deseado toda vez que dos líneas, por lo tanto ..

char localstr[MAX_STRLENGTH]; 
strcpy(localstr, somestr); 

Además, es una mala forma de incrustar una llamada gratuita() dentro de una función como esta. Idealmente, el free() debería ser visible en el mismo nivel de alcance donde ocurrió el malloc(). Por la misma lógica, es un poco dudoso asignar memoria en una función de esta manera.

Si desea una función para modificar una cadena, una convención común es algo como lo que

// use a prototype like this to use the same buffer for both input and output 
int modifyMyString(char buffer[], int bufferSize) { 
    // .. operate you find in buffer[], 
    // leaving the result in buffer[] 
    // and be sure not to exceed buffer length 
    // depending how it went, return EXIT_FAILURE or maybe 
    return EXIT_SUCCESS; 

// or separate input and outputs 
int workOnString(char inBuffer[], int inBufSize, char outBuffer[], int outBufSize) { 
    // (notice, you could replace inBuffer with const char *) 
    // leave result int outBuffer[], return pass fail status 
    return EXIT_SUCCESS; 

No incrustar malloc() o libre() en el interior también ayudará a evitar pérdidas de memoria.

+0

Copio y lo apunto a returnstr sin embargo ... ¿eso no evita el problema del alcance local? – chucknelson

+0

@chucknelson: podría ser un error ortográfico en el ejemplo, pero lo que se devuelve es un puntero al arreglo local, no el bloque recién asignado que se señala en 'returnstr'. –

+0

@MichaelBurr ack, tienes razón, arreglando ahora ... ¡gracias! – chucknelson

0

¿Su ejemplo de "actualización" está completo? No creo que compile: requiere un valor de retorno pero nunca devuelve nada. Nunca harás nada que sea un camino completo, pero tal vez sea deliberado, tal vez tu punto sea solo decir que cuando haces malloc, otras cosas se rompen.

Sin ver al que llama, es imposible decir definitivamente lo que está sucediendo aquí. Mi suposición es que las rutas es un bloque asignado dinámicamente que se liberó antes de llamar a esta función. Dependiendo de la implementación del compilador, un bloque libre todavía podría parecer que contiene datos válidos hasta que un malloc futuro tome el control del espacio.

Actualización: para realmente responder a la pregunta

manejo de cadenas es un problema bien conocido en C. Si se crea una matriz de tamaño fijo para contener la cadena, usted tiene que preocuparse de una larga cadena de desbordamiento el espacio asignado. Esto significa verificar constantemente los tamaños de cadena en las copias, utilizando strncpy y strncat en lugar de strcpy simple y strcat, o técnicas similares.Puede omitir esto y simplemente decir: "Bueno, nadie podría tener un nombre de más de 60 caracteres" o algo así, pero siempre existe el peligro de que alguien lo haga. Incluso en algo que debería tener un tamaño conocido, como un número de seguridad social o un ISBN, alguien podría cometer un error al ingresar y pulsar una tecla dos veces, o un usuario malintencionado podría ingresar algo deliberadamente. Etc. Por supuesto, esto es principalmente un problema en la entrada de datos o lectura de archivos. Una vez que tienes una cadena en un campo de un tamaño conocido, entonces para cualquier copia u otra manipulación, conoces el tamaño.

La alternativa es utilizar almacenamientos intermedios asignados dinámicamente donde puede hacerlos tan grandes como sea necesario. Esto suena como una buena solución cuando lo escuchas por primera vez, pero en la práctica es un dolor gigante en C, porque asignar los búferes y liberarlos cuando ya no los necesitas es un montón de problemas. Otro cartel aquí dice que la función que asigna un buffer debe ser la misma que lo libera. Una buena regla general, generalmente estoy de acuerdo, pero ... ¿Qué pasa si una subrutina quiere devolver una cadena? Entonces asigna el buffer, lo devuelve, y ... ¿cómo puede liberarlo? No puede porque todo el asunto es que quiere devolverlo a la persona que llama. La persona que llama no puede asignar el búfer porque no conoce el tamaño. Además, cosas aparentemente simples como:

if (strcmp(getMeSomeString(),stringIWantToCompareItTo)==0) etc 

son imposibles. Si la función getMeSomeString asigna la cadena, seguro, puede devolverla para que hagamos la comparación, pero ahora hemos perdido el control y nunca podemos liberarlo. Se termina por tener que escribir código difíciles como

char* someString=getMeSomeString(); 
int f=strcmp(someString,stringIWantToCompareItTo); 
free(someString); 
if (f==0) 
etc 

Así bien, funciona, pero la legibilidad de disminuir, simplemente.

En la práctica, he encontrado que cuando se puede esperar razonablemente que las cadenas sean de un tamaño cognoscible, asigno búferes de longitud fija. Si una entrada es más grande que el búfer, ya sea truncarlo o dar un mensaje de error, dependiendo del contexto. Solo recurro a los buffers asignados dinámicamente cuando el tamaño es potencialmente grande e impredecible.

+0

Sí, era solo un ejemplo para mostrar cuándo sería extraño. Voy con el enfoque de usar parámetros de salida, y declaro que las matrices de caracteres de tamaño fijo pasen a las funciones, y luego leo el resultado de ellas. – chucknelson

Cuestiones relacionadas