Can GCC warn me about modifying the fields of a co

2019-02-03 22:57发布

问题:

I stumbled upon a small issue while trying to make const-correct code.

I would have liked to write a function that takes a pointer to a const struct, to tell to the compiler "please tell me if I am modifying the struct, because I really do not want to".

It suddenly came to my mind that the compiler will allow me to do this:

struct A
{
    char *ptrChar;
};

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

Which is understandable, because what actually is const is the pointer itself, but not the type it points to. I would like for the compiler to tell me that I'm doing something I do not want to do, though, if that is even possible.

I used gcc as my compiler. Although I know that the code above should be legal, I still checked if it would issue a warning anyways, but nothing came. My command line was:

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

Is it possible to get around this issue?

回答1:

A way to design your way around this, if needed, is to use two different types for the same object: one read/write type and one read-only type.

typedef struct
{
  char *ptrChar;
} A_rw;

typedef struct
{
  const char* ptrChar;
} A_ro;


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

If a function needs to modify the object, it takes the read-write type as parameter, otherwise it takes the read-only type.

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

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

To pretty up the caller interface and make it consistent, you can use wrapper functions as the public interface to your ADT:

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

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

With this method, A can now be implemented as opaque type, to hide the implementation for the caller.



回答2:

This is an example of implementation vs. interface, or "information hiding" -- or rather non-hiding ;-) -- issue. In C++ one would simply have the pointer private and define suitable public const accessors. Or one would define an abstract class -- an "interface" -- with the accessor. The struct proper would implement that. Users who do not need to create struct instances would only need to see the interface.

In C one could emulate that by defining a function which takes a pointer to the struct as parameter and returns a pointer to const char. For users who do not create instances of these structs, one could even provide a "user header" which does not leak the struct's implementation but only defines manipulating functions taking (or returning, like a factory) pointers. This leaves the struct an incomplete type (so that only pointers to instances could be used). This pattern effectively emulates what C++ does behind the scenes with the this pointer.



回答3:

This is a known issue of the C language and not avoidable. After all, you are not modifying the structure, you are modifying a separate object through a non const-qualified pointer that you obtained from the structure. const semantic was originally designed around the need to mark memory regions as constant that physically aren't writable, not around any concerns for defensive programming.



回答4:

Maybe if you decide to use C11 you can implement a Generic macro which refers either to the constant or variable version of the same member (you should also include a union in your structure). Something like this:

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

The union creates 2 interpretation for a single member. The m_cptrChar is a pointer to constant char and the m_ptrChar to non-constant. Then the macro decides which to refer depending on the type of it's parameter.

The only problem is that the macro ptrChar_m can work only with either a pointer or object of this structure and not the both.



回答5:

We could hide the information behind some "accessor" functions:

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


回答6:

No, unless you change the struct definition to:

struct A
{
    const char *ptrChar;
};

Another convoluted solution that keeps the old struct definition intact, is to define a new struct with identical members, whose relevant pointer members are set to: points to const type. Then the function you are calling is changed to take the new struct. A wrapper function is defined that takes the old struct, does a member by member copy to the new struct and passes it to the function.



回答7:

Can GCC warn me about modifying the fields of a const struct in C99?

You are not modifying the fields of a const struct.

A value of struct A contains a pointer to a non-const char. ptrA is a pointer to a const struct A. So you can't change the struct A value at *ptrA. So you can't change the pointer to char at (*ptrA).Char aka ptrA->ptrChar. But you are changing the value at where ptrA->ptrChar points, ie the value at *(ptrA->Char) aka ptrA->Char[0]. The only consts here are struct As and you're not changing a struct A so what exacty is "not desired"?

If you don't want to allow change to the value at where a struct A's Char field points (via that struct A) then use

struct A
{
    const char *ptrChar; // or char const *ptrChar;
};

But maybe what you think you are doing in f is something like

void f(const struct A *ptrA)
{
    const char c = 'A';
    ptrA->ptrChar = &c;
}

Which will get an error from the compiler.