c pointers type-safety

Cuándo usar const void*?



pointers type-safety (3)

int test(const int* dummy) { *(char*)dummy = 1; return 0; }

No, esto no funciona. Desactivar constness (con datos verdaderamente const ) es un comportamiento indefinido y su programa probablemente se bloqueará si, por ejemplo, la implementación pone datos const en ROM. El hecho de que "funcione" no cambia el hecho de que su código está mal formado.

Al menos para mí, cuando utilicé void *, tiendo a convertirlo en char * para aritmética de punteros en pilas o para rastrear. ¿Cómo puede const void * impedir que las funciones de subrutina modifiquen los datos a los que apunta vpointer?

Un const void* significa un puntero a algunos datos que no se pueden cambiar. Para leerlo, sí, tienes que convertirlo en tipos concretos como char . Pero dije leer , no escribir , que, nuevamente, es UB.

Esto está cubierto más a fondo here . C le permite eludir por completo la seguridad de tipo: es su trabajo prevenir eso.

Tengo esta función de prueba muy simple que estoy usando para descubrir qué está pasando con el calificador const.

int test(const int* dummy) { *dummy = 1; return 0; }

Este me arroja un error con GCC 4.8.3. Sin embargo, este compila:

int test(const int* dummy) { *(char*)dummy = 1; return 0; }

Por lo tanto, parece que el calificador const funciona solo si uso el argumento sin transmitir a otro tipo.

Recientemente he visto códigos que usaban

test(const void* vpointer, ...)

Al menos para mí, cuando utilicé void *, tiendo a convertirlo en char * para aritmética de punteros en pilas o para rastrear. ¿Cómo puede const void * impedir que las funciones de subrutina modifiquen los datos a los que apunta vpointer ?


Es posible que un compilador determinado en un sistema operativo determinado pueda poner algunos de sus datos const en páginas de memoria de solo lectura. Si es así, intentar escribir en esa ubicación fallaría en el hardware, como causar un error de protección general.

El calificador const simplemente significa que escribir allí es un comportamiento indefinido . Esto significa que el estándar de idioma permite que el programa se cuelgue si lo hace (o cualquier otra cosa). A pesar de eso, C te permite dispararte en el pie si crees que sabes lo que estás haciendo.

No puede evitar que una subrutina reinterprete los bits que le da, como quiera, y ejecutar las instrucciones de la máquina que desee. La función de biblioteca que está llamando podría incluso escribirse en ensamblador. Pero hacer eso con un puntero const es un comportamiento indefinido , y realmente no desea invocar un comportamiento indefinido .

Fuera de mi cabeza, un raro ejemplo en el que podría tener sentido: supongamos que tienes una biblioteca que pasa por los parámetros de control. ¿Cómo genera y usa? Internamente, podrían ser indicadores de estructuras de datos. Así que esa es una aplicación en la que podrías typedef const void* my_handle; por lo que el compilador arrojará un error si sus clientes intentan desreferenciarlo o hacer aritmética sobre él por error, luego devolverlo a un puntero a su estructura de datos dentro de las funciones de su biblioteca. No es la implementación más segura, y debe tener cuidado con los atacantes que pueden pasar valores arbitrarios a su biblioteca, pero tiene un costo muy bajo.


const int *var;

const es un contrato . Al recibir un parámetro const int * , "le dice" a la persona que llama que usted (la función llamada) no modificará los objetos a los que apunta el puntero.

Su segundo ejemplo explícitamente rompe ese contrato descartando el calificador const y luego modificando el objeto apuntado por el puntero recibido. Nunca hagas esto

Este "contrato" es aplicado por el compilador. *dummy = 1 no compilará. El reparto es una forma de eludir eso, diciéndole al compilador que realmente sabes lo que estás haciendo y permitiéndote hacerlo. Lamentablemente, "realmente sé lo que estoy haciendo" generalmente no es el caso.

const también puede ser utilizado por el compilador para realizar la optimización que de otro modo no podría.

Nota de comportamiento indefinido:

Tenga en cuenta que si bien el elenco en sí es técnicamente legal, la modificación de un valor declarado como const es un comportamiento indefinido. De modo que, técnicamente, la función original está bien, siempre que el puntero que se le pase apunte a datos declarados mutables. De lo contrario, es un comportamiento indefinido.

más sobre esto al final de la publicación

En cuanto a la motivación y el uso, tomemos los argumentos de las funciones strcpy y memcpy :

char* strcpy( char* dest, const char* src ); void* memcpy( void* dest, const void* src, std::size_t count );

strcpy opera en cadenas de caracteres, memcpy opera en datos genéricos. Mientras uso strcpy como ejemplo, la siguiente discusión es exactamente la misma para ambos, pero con char * y const char * para strcpy y void * y const void * para memcpy :

dest es char * porque en el buffer de la función pondrá la copia. La función modificará el contenido de este búfer, por lo tanto, no es const.

src es const char * porque la función solo lee el contenido del buffer src . No lo modifica

Solo al observar la declaración de la función, un llamante puede afirmar todo lo anterior. Por contrato strcpy no modificará el contenido del segundo buffer pasado como argumento.

const y void son ortogonales. Esa es toda la discusión anterior sobre const aplica a cualquier tipo ( int , char , void , ...)

void * se usa en C para datos "genéricos".

Aún más en Comportamiento Indefinido:

Caso 1:

int a = 24; const int *cp_a = &a; // mutabale to const is perfectly legal. This is in effect // a constant view (reference) into a mutable object *(int *)cp_a = 10; // Legal, because the object referenced (a) // is declared as mutable

Caso 2

const int cb = 42; const int *cp_cb = &cb; *(int *)cp_cb = 10; // Undefined Behavior. // the write into a const object (cb here) is illegal.

Comencé con estos ejemplos porque son más fáciles de entender. Desde aquí solo hay un paso para funcionar argumentos:

void foo(const int *cp) { *(int *)cp = 10; // Legal in case 1. Undefined Behavior in case 2 }

Caso 1:

int a = 0; foo(&a); // the write inside foo is legal

Caso 2

int const b = 0; foo(&b); // the write inside foo causes Undefined Behavior

De nuevo debo enfatizar: a menos que realmente sepas lo que estás haciendo, y todas las personas que trabajan en el presente y en el futuro en el código son expertos y entienden esto, y tienes una buena motivación, a menos que se cumplan todas las condiciones anteriores, nunca descarta la constness !!