Подтвердить что ты не робот

Тип безопасности в 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 и достижения желаемого результата, мне просто интересно, был ли более элегантный способ сделать это.
Может быть, это немного больше?

4b9b3361

Ответ 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'