39 votes

Type-sécurité en C

Existe-t-il un moyen de rendre C un peu plus conscient des types et d’assurer leur sécurité?
Considère ceci:

 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;
}
 

Est-il possible de faire en sorte que le code ci-dessus au moins déclenche un avertissement par le gcc?
Je sais que je peux utiliser les structures C pour encapsuler unsigned s et obtenir le résultat souhaité. Je me demandais simplement s'il existait un moyen plus élégant de le faire.
Cela peut-il être un peu plus que ça?

31voto

Lundin Points 21616

Le problème est que le C ne permet pas de traiter vos deux typedefs que les types distinctifs, parce qu'ils sont à la fois de type unsigned.

Il existe différentes astuces pour esquiver cette. Une chose serait de changer votre types d'énumérations. Bon compilateurs appliquer plus fort en tapant des avertissements sur les conversions implicites à partir d'un certain type d'énumération de tout autre type.

Même si vous n'avez pas un bon compilateur, avec les énumérations, vous pourriez faire ceci:

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;
}

Plus classique/traditionnel astuce consiste à utiliser un générique struct wrapper, où vous utilisez un "ticket" enum pour marquer le type. Exemple:

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)
  ...
}

L'avantage est qu'il fonctionne avec n'importe quel C version. L'inconvénient est code et de la surcharge de la mémoire, et qu'elle permet uniquement au moment de l'exécution des contrôles.

10voto

n.m. Points 30344

L'Aliasing a une signification étroite dans C, et ce n'est pas ce que vous avez à l'esprit. Vous voudrez peut-être dire "typedefing".

Et la réponse est non, vous ne pouvez pas. Pas d'une manière élégante à n'importe quel taux. Vous pouvez utiliser une structure pour chaque type numérique, et d'un ensemble de fonctions pour faire de l'arithmétique avec chacun d'eux. Sauf quand il s'agit de la multiplication, vous êtes hors de la chance. Afin de multiplier les pieds par des livres, vous avez besoin d'un troisième type. Vous avez également besoin de types de pieds carrés, pieds cube, seconde à la puissance moins deux et d'un nombre infini d'autres types.

Si c'est ce que vous êtes après, C est pas la bonne langue.

9voto

Andy Sinclair Points 96

Pour ce faire, vous devez utiliser un outil d'analyse statique dans votre processus de construction.

Par exemple, si vous exécutez PCLint sur votre code, cela donne le résultat suivant:

   [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

7voto

tristopia Points 5074

EDIT: Voici une alternative qui fonctionne même en C89, dans le cas où votre compilateur ne supporte pas l' _Generic du sélecteur (beaucoup de compilateurs ne pas et bien souvent, vous êtes coincé avec ce qui est installé sur votre machine).

Vous pouvez utiliser des macros pour simplifier l'utilisation d' struct des wrappers.

#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;
}

Vous obtenez encore plus fort qu'un avertissement. Voici le résultat de la compilation avec gcc 5.1

$ gcc-O3 -Mur Edit1.c
Edit1.c: In function ‘main':
Edit1.c:17:10: error: incompatible type de l'argument 1 of ‘calc'
 calc(montant); // relance d'avertissement
^
Edit1.c:10:6: note: attendu " cent_t {aka struct }", mais l'argument est de type ‘dollar_t {aka struct }'
 void calc(cent_t montant);// {

et voici le résultat avec gcc 3.4

$ gcc-O3 -Mur Edit1.c
Edit1.c: In function 'main':
Edit1.c:17: error: incompatible type de l'argument 1 of 'calc'

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X