Тип безопасности в C
Есть ли способ сделать C немного более осведомленным о типах и обеспечить безопасность типов?
Рассмотрим это:
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;
}
Есть ли способ сделать вышеуказанный код минимальным предупреждением gcc?
Я знаю, что могу использовать C-structs для переноса unsigned
и достижения желаемого результата, мне просто интересно, был ли более элегантный способ сделать это.
Может быть, это немного больше?
Ответы
Ответ 1
Для достижения этой цели вам необходимо использовать инструмент статического анализа в процессе сборки.
Например, если вы запустите PCLint на свой код, он даст следующий результат:
[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
http://www.gimpel.com/html/strong.htm
Ответ 2
Проблема заключается в том, что C не обрабатывает ваши два typedef как отличительные типы, потому что они оба типа unsigned
.
Существуют различные трюки, чтобы уклониться от этого. Одно дело было бы изменить ваши типы на перечисления. Хорошие компиляторы будут применять более строгие предупреждения о типизации для неявных преобразований в/из определенного типа перечисления в любой другой тип.
Даже если у вас нет хорошего компилятора, с перечислениями вы можете сделать это:
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;
}
Более традиционным/традиционным трюком является использование общей обертки структуры, в которой вы используете "билет" для перечисления типа. Пример:
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)
...
}
Преимущество заключается в том, что он работает с любой версией C. Недостатком является нехватка кода и памяти, и что он позволяет выполнять проверки во время выполнения.
Ответ 3
Алиасинг имеет очень специфический узкий смысл в C, и это не то, что вы имеете в виду. Вы можете сказать "typedefing".
И ответ - нет, вы не можете. Во всяком случае, не изящно. Вы можете использовать структуру для каждого числового типа и отдельный набор функций для выполнения арифметики с каждым из них. За исключением случаев, когда дело доходит до умножения, вам не повезло. Чтобы умножить ноги на килограммы, вам нужен третий тип. Вам также нужны типы для футов в квадрате, футы в кубе, секунды до мощности минус два и бесконечное количество других типов.
Если это то, что вам нужно, C не является правильным языком.
Ответ 4
EDIT: Здесь альтернатива, которая работает даже на C89, если ваш компилятор не поддерживает селектор _Generic
(многие компиляторы не часто и часто зацикливаются на том, что установлено на вашем компьютере).
Вы можете использовать макросы, чтобы упростить использование оберток 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;
}
Вы становитесь еще сильнее, чем предупреждение. Вот результат компиляции с 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);// {
и здесь результат с gcc 3.4
$ gcc -O3 -Wall Edit1.c
Edit1.c: In function 'main':
Edit1.c:17: error: incompatible type for argument 1 of 'calc'