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

六眼飞鱼酱① 提交于 2019-11-30 05:38:36
Lundin

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.

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.

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.

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.

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

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!