2010-07-23 12 views
6

Tengo una función que reasigna un puntero dado como argumento a un nuevo tamaño. Ahora, el problema es que, de acuerdo con la página del manual, realloc necesita un puntero que haya sido devuelto por malloc o calloc anteriormente.¿Cómo puedo asegurarme de que una persona que llama pasa un puntero malloc?

¿Cómo puedo asegurarme de que la persona que llama pasa un puntero que cumpla con esos requisitos? Parece que no hay mecanismos de C incorporados (como calificadores de tipo o algo así) para hacerlo.

Ahora, antes de reestructurar mi API (ya que considero que la función ya no es lo suficientemente robusta), ¿puede verificar que no me he perdido algo?

Gracias de antemano.

Editar: Una solución sería evidentemente a malloc en la función. El problema con eso es que la persona que llama no "ve" la asignación. Por lo tanto, tendría que decir explícitamente en los documentos que tiene que liberar el puntero. Eso es incluso peor que esperar que proporcionen un puntero malloc (lo que implicaría que la persona que llama tiene que liberarlo).

Lo que realmente quiero es algo que bloquee el abuso en tiempo de compilación. Eso, y un pony. ;-)

+0

¿Es necesario para que sea realmente tan a prueba de tontos? – Nyan

+0

al menos me gustaría, sí – RWS

+0

Como no puedes, probablemente deberías dejar de preocuparte y hacer algo más interesante. Las personas pueden subvertir ** cualquier ** o ** toda ** comprobación o prevención que desee probar. No pierdas tiempo previniendo. Invierta tiempo en estrellarse alto y claro para que puedan leer el mensaje de error y darse cuenta de su error. –

Respuesta

10

¿Cómo puedo asegurarme de que la persona que llama pasa un puntero que cumpla con esos requisitos?

Documentación.

Define la API.

Si la persona que escribe la persona que llama se niega a seguir la API, las cosas se cuelgan. Se negaron a jugar según tus reglas y las cosas se cayeron. ¿Qué esperaban?

2

No se puede determinar si el área a la que apunta un puntero ha sido [m|c]alloc(); 'd. Solo puedes definir tu API y esperar que los usuarios de API la sigan.

¿Está asignando memoria usted mismo (callee) prohibitivo? Todavía puede hacer la asignación inicial con realloc(NULL, size); si está obligado por eso.

Más información sobre su problema sería bueno.

0

Puede mantener un puntero estático dentro de su función y llamar al realloc usted mismo. Devuelve el puntero estático. De esta forma, el usuario no tiene problemas con la asignación o la liberación. La variable se liberará automáticamente cuando el proceso muera.

Tenga en cuenta que si realloc falla, devuelve un puntero nulo pero la memoria original malloc'ed queda intacta, esta es una fuente común de pérdidas de memoria.

+0

Esto no responde la pregunta. –

+2

No, pero es una nota importante que es aplicable a cualquier respuesta, por lo que la superé, aunque preferiría decir "if (tmp) a = tmp; else {tener en cuenta el error pero dejar un tal como está}" . Si el realloc falla, nadie debe tratar de usar el área "expandida" del espacio asignado, pero quienquiera que posea el puntero antes debe ser el que lo elimine. – supercat

4

Al final del día, los punteros en C no son más que una palabra de máquina, que puede o no señalar datos abundantes dentro de la memoria del proceso, que pueden o no pueden ser asignados por malloc. No hay tal información adjunta a ningún puntero.

Solo agregue una advertencia grande y adversa a la API. Si la documentación dice claramente "nunca pases este puntero a la función que no estaba malloc", y alguien lo hace y obtiene errores/crashs, no es tu culpa, no hay manera de que puedas programar defensivamente aquí.

0

Cambia tu implementación para que no uses realloc. Si el búfer pasado es demasiado pequeño, simplemente malloc uno nuevo más grande y memcpy el contenido del buffer pasado a él. Esto mejorará la usabilidad de su API a expensas de una pérdida marginal de rendimiento.

+1

Eso es aún peor. Ahora debe confiar en que la persona que llama libere() el nuevo búfer. Si no lo hacen, no se estrellará ruidosamente; solo perderá memoria. Si desea protegerse de los usuarios que no leen documentos API, no lo haga de forma que falle silenciosamente. – nmichaels

+0

@Nathon: Si vuelve a colocar un puntero en una llamada API, es potencialmente mucho peor que mallocing la memoria nueva. El puntero original ahora puede no ser válido, pero si el programador no ha leído la documentación de la API, es posible que no lo sepa y decida escribirle más datos. Eso podría conducir fácilmente a sutiles errores de corrupción de datos, si, por ejemplo, el antiguo puntero se encuentra ahora parcialmente en un nuevo buffer diferente. – JeremyP

2

Una posible forma. Resúmalo.

foo.c 
#include "foo.h" 
struct foo{ 
    ... 
} ; 

foo *newFoo(...)   
void resize(foo *f)  

foo.h 

struct foo; 
typedef struct foo; 

caller.c 

foo *myfoo; 
myfoo = newFoo(..) 
resize(myfoo); 
+0

+1 Creo que acabo de responder lo mismo que ya lo hizo – asr

0

Si todo lo que tiene es el valor del puntero, no hay manera (estándar) para determinar si el valor del puntero fue devuelto por malloc(). Si tiene un profundo conocimiento de cómo funciona la asignación dinámica de memoria en su plataforma, puede hacer una estimación basada en el valor del puntero, pero incluso eso no es garantía de que esté viendo algo que en realidad fue un valor de retorno de malloc() como opuesto a un valor compensado de eso (p. ej., *ptr = malloc(10); foo(&ptr[5]);).

Una de las estrategias que pueden o no la pena el esfuerzo (IME, no lo es) es ocultar malloc(), realloc(), y free() detrás de algún tipo de administrador de memoria que realiza un seguimiento de punteros y tamaños asignados. En lugar de llamar al realloc(), su código llamaría a la función de cambio de tamaño en el administrador de memoria, que devolvería un código de error si el puntero que se le pasó no está en la lista de punteros administrados.

2

¿Cómo se supone que el usuario obtendrá este puntero malloc'ed en primer lugar? ¿No es posible que se obtenga con otra función en su API? De lo contrario, me suena como un código repetitivo, teniendo que asignar un trozo de memoria con malloc antes de llamar a tu función.

En su situación, lo intentaría. Hacer que la persona que llama obtenga un puntero válido a través de una función que proporciono y soltarlo a través de otra función. Aún mejor, envuélvalos todos juntos en una estructura opaca.

my_param_type my_param = init_param(....); 
... 
my_function_that_wants_malloc(.....,my_param); 
... 
free_param(my_param); 

¿Tiene sentido en su API?

Teniendo su propio tipo, estará claro incluso para el usuario más cojo que tiene que obtenerlo a través de la función init_param() esperada.

0

Cree un tipo de datos personalizado que sea un puntero de vacío renombrado.

typedef void* myPointerType; 

Escribir una función que asigne (que puede ser un simple envoltorio alrededor malloc) que el usuario debe utilizar para asignar memoria.

myPointerType myAllocateFunction (size_t size) { 
    return (myPointerType)(malloc(size)); 
} 

También querrá hacer una función "libre" que coincida.

Ahora, tenga su función (la que realiza el realloc) tome un objeto myPointerType como parámetro en lugar de un puntero genérico. Puede devolverlo a void* para usar con realloc. Para evitar esto, el usuario debería lanzar manualmente un puntero ed no malloc a myPointerType, pero puede especificar en su documentación que no se permite la conversión ay desde myPointerType (por lo que si hacen esto y la aplicación falla, es porque abusaron de la API).

Existen formas aún más poderosas para hacer cumplir esto, pero no estoy seguro de si valdría la pena. No se puede engañar completamente a una API (se sorprendería de cuán capaces son los tontos en estos días).Independientemente de lo que termine implementando, la mejor manera de garantizar que su API se use correctamente es proporcionar documentación clara y completa.

Estás por tu cuenta para el poni, lo siento.

+0

¿Ese typedef va en un archivo * .h cuando se compila como lib estático/compartido? Lo siento nueva pregunta. –

+0

@ TristonJ.Taylor- Si esto es parte de la interfaz pública de su biblioteca compartida, entonces sí, debe ir en el archivo de encabezado. De lo contrario, tendrá funciones que hacen referencia a tipos que no se han definido y sus usuarios recibirán errores de tiempo de compilación. – bta

0

Si busca una verificación en tiempo de compilación, lo único que realmente puede hacer es declarar algún tipo nuevo que sea devuelto por llamadas de asignación "aprobadas". Yo sugeriría algo así como:

 
typedef struct MEM_INFO {void *ptr; int allocsize; struct *privateMemInfo priv;} ALLOCMEM[0]; 

El privateMemInfo se define en el archivo .c, en lugar de la cabecera, protegiendo así tanto en los campos desde la travesura. Tenga en cuenta que dado que ALLOCMEM sería un tipo de matriz, se accedería siempre a ptr y allocsize usando el operador de flecha en lugar del punto. Una rutina que toma un ALLOCMEM como parámetro, naturalmente, se le da un puntero a la ALLOCMEM, lo que le permite realizar la reubicación "muy bien". Así, por ejemplo, se podría usar:

 
    ALLOCMEM foo = {0}; 
    if (reallocate(foo,12345) >= 0) /* It worked */ 
+0

Gran idea aquí, pero: typedef char * strptr; te derrota de verdad! Strptr puede convertirse en un char * y el CPP detectará el error en el momento de la compilación/diseño.En el caso de que alguien intente subvertir eso, oh sí, el realloc fracasará ruidosamente por sí mismo. Gran idea. Gracias. Creo que es lo que realmente buscan yo y el operador. –

+0

Mi declaración es incorrecta, ya que debe ser ALLOCMEM [1], pero la idea básica es que todas las rutinas de asignación requieren una referencia a "struct MEM_INFO". Si bien nada evitaría que el código se metiera con los contenidos de una "estructura MEM_INFO", la verificación en tiempo de compilación garantizaría que nada más que una referencia a dicha estructura podría pasarse a las rutinas de asignación/desasignación. – supercat

0

Así que tendría que decir explicitamente en la documentación que tiene que liberar el puntero . Eso es incluso peor que esperan que proporcionen un puntero malloc'd (lo que implicaría que el llamador tiene que liberarlo).

En librerías de C Es una práctica común:

Cuestiones relacionadas