37 votes

GCC peut-il m'avertir de la modification des champs d'un const struct en C99 ?

Je suis tombé sur un petit problème en essayant de faire un code correct du point de vue des constantes.

J'aurais aimé écrire une fonction qui prend un pointeur sur une structure constante, pour dire au compilateur "s'il vous plaît, dites-moi si je modifie la structure, parce que je ne veux vraiment pas le faire".

Il m'est soudain venu à l'esprit que le compilateur me permettra de le faire :

struct A
{
    char *ptrChar;
};

void f(const struct A *ptrA)
{
    ptrA->ptrChar[0] = 'A'; // NOT DESIRED!!
}

Ce qui est compréhensible, car ce qui est en fait const est le pointeur lui-même, mais pas le type vers lequel il pointe. J'aimerais cependant que le compilateur me dise que je fais quelque chose que je ne veux pas faire, si c'est possible.

J'ai utilisé gcc comme compilateur. Bien que je sache que le code ci-dessus devrait être légal, j'ai tout de même vérifié s'il émettrait un avertissement, mais rien n'est venu. Ma ligne de commande était :

gcc -std=c99 -Wall -Wextra -pedantic test.c

Est-il possible de contourner ce problème ?

19voto

Lundin Points 21616

Une façon de contourner ce problème, si nécessaire, est d'utiliser deux types différents pour le même objet : un type lecture/écriture et un type lecture seule.

typedef struct
{
  char *ptrChar;
} A_rw;

typedef struct
{
  const char* ptrChar;
} A_ro;

typedef union
{
  A_rw rw;
  A_ro ro;  
} A;

Si une fonction doit modifier l'objet, elle prend en paramètre le type lecture-écriture, sinon elle prend le type lecture seule.

void modify (A_rw* a)
{
  a->ptrChar[0] = 'A';
}

void print (const A_ro* a)
{
  puts(a->ptrChar);
}

Pour embellir l'interface de l'appelant et la rendre cohérente, vous pouvez utiliser des fonctions enveloppes comme interface publique de votre ADT :

inline void A_modify (A* a)
{
  modify(&a->rw);
}

inline void A_print (const A* a)
{
  print(&a->ro);
}

Avec cette méthode, A peut maintenant être implémenté comme un type opaque, afin de cacher l'implémentation pour l'appelant.

7voto

Peter Schneider Points 1913

Il s'agit d'un exemple de mise en œuvre par rapport à l'interface, ou de "dissimulation d'informations" - ou plutôt de non dissimulation ;-) -- question. En C++, il suffirait de rendre le pointeur privé et de définir des accesseurs publics const appropriés. Ou bien on peut définir une classe abstraite - une "interface" - avec l'accesseur. La structure proprement dite l'implémenterait. Les utilisateurs qui n'ont pas besoin de créer des instances de struct n'auraient besoin que de voir l'interface.

En C, on pourrait émuler cela en définissant une fonction qui prend un pointeur vers la structure comme paramètre et renvoie un pointeur vers const char. Pour les utilisateurs qui ne créent pas d'instances de ces structures, on pourrait même fournir un "en-tête utilisateur" qui ne divulgue pas l'implémentation de la structure mais définit uniquement des fonctions de manipulation prenant (ou retournant, comme une usine) des pointeurs. Ainsi, la structure reste un type incomplet (de sorte que seuls les pointeurs vers les instances peuvent être utilisés). Ce modèle émule efficacement ce que C++ fait dans les coulisses avec la structure de type this pointeur.

5voto

Peut-être que si vous décidez d'utiliser C11, vous pouvez implémenter une macro générique qui fait référence à la version constante ou variable du même membre (vous devriez également inclure une union dans votre structure). Quelque chose comme ceci :

struct A
{
    union {
        char *m_ptrChar;

        const char *m_cptrChar;
    } ;
};

#define ptrChar_m(a) _Generic(a, struct A *: a->m_ptrChar,        \
                                 const struct A *: a->m_cptrChar)//,  \
                                 //struct A: a.m_ptrChar,        \
                                 //const struct A: a.m_cptrChar)

void f(const struct A *ptrA)
{
    ptrChar_m(ptrA) = 'A'; // NOT DESIRED!!
}

L'union crée 2 interprétations pour un seul membre. Le site m_cptrChar est un pointeur sur un char constant et le m_ptrChar à non-constant. Ensuite, la macro décide de la référence en fonction du type de son paramètre.

Le seul problème est que la macro ptrChar_m ne peut fonctionner qu'avec un pointeur ou un objet de cette structure et non les deux.

5voto

FUZxxl Points 21462

Il s'agit d'un problème connu du langage C qui ne peut être évité. Après tout, vous ne modifiez pas la structure, vous modifiez un objet séparé par le biais d'une commande non const -Le pointeur qualifié que vous avez obtenu de la structure. const La sémantique a été conçue à l'origine pour répondre au besoin de marquer comme constantes des régions de mémoire qui ne sont pas physiquement accessibles en écriture, et non pour répondre à des préoccupations de programmation défensive.

3voto

chi Points 8104

Nous pourrions cacher l'information derrière certaines fonctions "accesseurs" :

// header
struct A;           // incomplete type
char *getPtr(struct A *);
const char *getCPtr(const struct A *);

// implementation
struct A { char *ptr };
char *getPtr(struct A *a) { return a->ptr; }
const char *getCPtr(const struct A *a) { return a->ptr; }

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