simples - tipos de datos primitivos en c
Tipo de seguridad en C (4)
¿Hay alguna manera de hacer que C sea un poco más consciente de los tipos y garantizar la seguridad de tipo?
Considera esto:
typedef unsigned cent_t;
typedef unsigned dollar_t;
#define DOLLAR_2_CENT(dollar) ((cent_t)(100*(dollar)))
void calc(cent_t amount) {
// expecting ''amount'' to semantically represents cents...
}
int main(int argc, char* argv[]) {
dollar_t amount = 50;
calc(DOLLAR_2_CENT(amount)); // ok
calc(amount); // raise warning
return 0;
}
¿Hay alguna manera de hacer que el código anterior al menos aumente la advertencia del gcc?
Sé que puedo usar C-structs para envolver unsigned
s y lograr el resultado deseado, me preguntaba si había una forma más elegante de hacerlo.
¿Puede ser un poco más que eso?
Aliasing tiene un significado estrecho muy específico en C, y no es lo que tienes en mente. Puede querer decir "typedefing".
Y la respuesta es no, no puedes. No de una manera elegante en cualquier caso. Puede usar una estructura para cada tipo numérico y un conjunto separado de funciones para hacer aritmética con cada uno. Excepto cuando se trata de multiplicación, no tienes suerte. Para multiplicar pies por libras, necesitas un tercer tipo. También necesita tipos para pies cuadrados, pies en cubos, segundos para la potencia de menos dos y un número infinito de otros tipos.
Si esto es lo que buscas, C no es el idioma correcto.
Debe usar una herramienta de análisis estático en su proceso de compilación para lograr esto.
Por ejemplo, si ejecuta PCLint en su código, proporciona esta salida:
[Warning 632] Assignment to strong type ''cent_t'' in context: arg. no. 1
[Warning 633] Assignment from a strong type ''dollar_t'' in context: arg. no. 1
EDITAR: Aquí hay una alternativa que funciona incluso en C89, en caso de que su compilador no admita _Generic
(muchos compiladores no lo hacen y, a menudo, está atascado con lo que está instalado en su máquina).
Puede usar macros para simplificar el uso de contenedores de struct
.
#define NEWTYPE(nty,oty) typedef struct { oty v; } nty
#define FROM_NT(ntv) ((ntv).v)
#define TO_NT(nty,val) ((nty){(val)}) /* or better ((nty){ .v=(val)}) if C99 */
NEWTYPE(cent_t, unsigned);
NEWTYPE(dollar_t, unsigned);
#define DOLLAR_2_CENT(dollar) (TO_NT(cent_t, 100*FROM_NT(dollar)))
void calc(cent_t amount) {
// expecting ''amount'' to semantically represents cents...
}
int main(int argc, char* argv[]) {
dollar_t amount = TO_NT(dollar_t, 50); // or alternatively {50};
calc(DOLLAR_2_CENT(amount)); // ok
calc(amount); // raise warning
return 0;
}
Te vuelves aún más fuerte que una advertencia. Aquí está el resultado de la compilación con gcc 5.1
$ gcc -O3 -Wall Edit1.c Edit1.c: In function ‘main’: Edit1.c:17:10: error: incompatible type for argument 1 of ‘calc’ calc(amount); // raise warning ^ Edit1.c:10:6: note: expected ‘cent_t {aka struct }’ but argument is of type ‘dollar_t {aka struct }’ void calc(cent_t amount);// {
y aquí el resultado con gcc 3.4
$ gcc -O3 -Wall Edit1.c Edit1.c: In function ''main'': Edit1.c:17: error: incompatible type for argument 1 of ''calc''
El problema es que C no trata sus dos typedefs como tipos distintivos, porque ambos son de tipo unsigned
.
Hay varios trucos para esquivar esto. Una cosa sería cambiar tus tipos a enums. Los buenos compiladores aplicarán advertencias de tipado más fuertes en las conversiones implícitas desde / hacia un cierto tipo de enumeración a cualquier otro tipo.
Incluso si no tienes un buen compilador, con enumeraciones puedes hacer esto:
typedef enum { FOO_CENT } cent_t;
typedef enum { FOO_DOLLAR} dollar_t;
#define DOLLAR_2_CENT(dollar) ((cent_t)(100*(dollar)))
void calc(cent_t amount) {
// expecting ''amount'' to semantically represents cents...
}
#define type_safe_calc(amount) _Generic(amount, cent_t: calc(amount))
int main(int argc, char* argv[]) {
dollar_t amount = 50;
type_safe_calc(DOLLAR_2_CENT(amount)); // ok
type_safe_calc(amount); // raise warning
return 0;
}
Un truco más convencional / tradicional es usar un envoltorio genérico de estructuras, donde se usa una enumeración "ticket" para marcar el tipo. Ejemplo:
typedef struct
{
type_t type;
void* data;
} wrapper_t;
...
cent_t my_2_cents;
wrapper_t wrapper = {CENT_T, &my_2_cents};
...
switch(wrapper.type)
{
case CENT_T: calc(wrapper.data)
...
}
La ventaja es que funciona con cualquier versión C. La desventaja es la sobrecarga de código y memoria, y solo permite verificaciones en tiempo de ejecución.