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.