Take the 2-minute tour ×

In order to have a clean code, using some OO concept can be usefull, even in C. I often write modules made of a pair of .h and .c files. The problem is that the user of the module have to be careful, since private members don't exist in C. The use of the pimpl idiom or abstract data types is ok, but it adds some code and/or files, and requires a heavier code. I hate using accessor when I don't need one.

Here is a idea wich provides a way to make the compiler complain about invalid access to "private" members, with only a few extra code. The idea is to define twice the same structure, but with some extra 'const' added for the user of the module.

Of course, writing in "private" members is still possible with a cast. But the point is only to avoid mistakes from the user of the module, not to safely protect memory.

/*** 2DPoint.h module interface ***/
#ifndef H_2D_POINT
#define H_2D_POINT

/* 2D_POINT_IMPL need to be defined in implementation files before #include */
#ifdef 2D_POINT_IMPL
#define _cst_
#define _cst_ const

typedef struct 2DPoint
    /* public members: read and write for user */
    int x;

    /* private members: read only for user */
    _cst_ int y;
} 2DPoint;

2DPoint *new_2dPoint(void);
void delete_2dPoint(2DPoint **pt);
void set_y(2DPoint *pt, int newVal);

/*** 2dPoint.c module implementation ***/
#define 2D_POINT_IMPL
#include "2dPoint.h"
#include <stdlib.h>
#include <string.h>

2DPoint *new_2dPoint(void)
    2DPoint *pt = malloc(sizeof(2DPoint));
    pt->x = 42;
    pt->y = 666;

    return pt;

void delete_2dPoint(2DPoint **pt)
    *pt = NULL;

void set_y(2DPoint *pt, int newVal)
    pt->y = newVal;

#endif /* H_2D_POINT */

/*** main.c user's file ***/
#include "2dPoint.h"
#include <stdio.h>
#include <stdlib.h>

int main(void)
    2DPoint *pt = new_2dPoint();

    pt->x = 10;     /* ok */
    pt->y = 20;     /* Invalid access, y is "private" */    
    set_y(pt, 30);  /* accessor needed */
    printf("pt.x = %d, pt.y = %d\n", pt->x, pt->y);  /* no accessor needed for reading "private" members */


    return EXIT_SUCCESS;

And now, here is the question: is this trick OK with the C standard? It works fine with GCC, and the compiler doesn't complain about anything, even with some strict flags, but how can I be sure that this is really OK?

share|improve this question
Interesting approach. Whether it's well-defined behaviour I don't know. I'd recommend against it because it's far from idiomatic C... either use an opaque struct (defined in the .c file) and provide accessors, or document that fields aren't to be assigned to. –  Thomas Dec 26 '12 at 16:49
I think Thomas answer should go as a "real" answer - perhaps with a couple of examples. –  Mats Petersson Dec 26 '12 at 16:52
How does 2DPoint form a valid identifier, by the way? –  user529758 Dec 26 '12 at 16:55
+1 for a good question. –  Happy Dec 26 '12 at 17:08
I elaborated on Thomas' answer (accidentally, didn't see the comment) under H2CO3's answer, if you want a better idea of how opaque structs work. –  Jonathan Grynspan Dec 26 '12 at 17:28

4 Answers 4

up vote 3 down vote accepted

This violates C 2011 6.2.7 1.

6.2.7 1 requires that two definitions of the same structure in different translation units have compatible type. It is not permitted to have const in one and not the other.

In one module, you may have a reference to one of these objects, and the members appear to be const to the compiler. When the compiler writes calls to functions in other modules, it may hold values from the const members in registers or other cache or in partially or fully evaluated expressions from later in the source code than the function call. Then, when the function modifies the member and returns, the original module will not have the changed value. Worse, it may use some combination of the changed value and the old value.

This is highly improper programming.

share|improve this answer
@H2CO3, can you explain a bit better. Contrary to your answer, this one here give a correct answer, including a reference to standard that shows why this is UB. –  Jens Gustedt Dec 26 '12 at 22:02
@JensGustedt Have a look at the edit. Originally Eric provided an answer regarding C++. Since then he deleted his comments and edited his answer. I have deleted my now-obsolete comment as well. –  user529758 Dec 26 '12 at 22:05
@H2CO3, even if previously this was an answer for C++, the ideas about this seem to be similar in the two languages, only the wording is different. In any case, now this answer here uses the correct wording, and it gives a good motivation, not only why this is UB, but also why these two different types may really have a different layout. –  Jens Gustedt Dec 26 '12 at 22:14
@JensGustedt Yes, that's why I removed my comment. Previously it was you who were complaining about wording, so let's not continue this pointless discussion. I was right, Eric is also right, you don't badger anyone anymore and there'll be peace. Bye. –  user529758 Dec 26 '12 at 22:17

This is almost certainly undefined behavior.

Writing/modifying an object declared as const is prohibited and doing so results in UB. Furthermore, the approach you take re-declares struct 2DPoint as two technically different types, which is also not permitted.

Note that this (as undefined behavior in general) does not mean that it "certainly won't work" or "it must crash". In fact, I find it quite logical that it works, because if one reads the source intelligently, he may easily find out what the purpose of it is and why it migh be regarded as correct. However, the compiler is not intelligent - at best, it's a finite automaton which has no knowledge about what the code is supposed to do; it only obeys (more or less) to the syntactical and semantical rules of the grammar.

share|improve this answer
This is certainly undefined behavior (not almost) ;) –  netcoder Dec 26 '12 at 17:11
@H2CO3, it wasn't me but was tempted, too. Your answer is just imprecise on the border of being false. "Identifiers" in C certainly don't have a type or can be const-qualified. Objects have a type, that can be const qualified, and it is UB to access such an object of const-qualified type. So this rule alone wouldn't make the approach in the question UB. Eric is giving the correct answer. –  Jens Gustedt Dec 26 '12 at 21:59
@H2CO3 wrong. The type of the object is determined in the compilation unit where there is no const, so the type of the object is not const-qualified. So it is valid to pass such an object back into that compilation unit to modify it. To say it again, your idea why this would be UB was wrong, Eric gave the correct answer and your's is now copying that, without giving reference to Eric's. –  Jens Gustedt Dec 26 '12 at 22:08
@H2CO3: There is no such thing in the C standard as a const-qualified object. There are const-qualified types. There is a rule against accessing an object defined with a const-qualified type with an lvalue of non-const-qualified type, but that would not apply here since the object would be defined with the non-const type, and the const type would only be used for access. Cite the exact rule in C that is violated. –  Eric Postpischil Dec 26 '12 at 22:10
[Corrected typo.] @H2CO3: Are you retracting your statement that “since the object is const qualified and it is [modified], that's what invokes UB”? Because the object is not const-qualified (since there is no such thing), and the object is not defined by a const-qualified type, so modifying it is not by itself undefined behavior. If you are not retracting it, please state the specific clause of the C standard that is violated. –  Eric Postpischil Dec 27 '12 at 1:32

In Bjarne Stroustrup's words: C is not designed to support OOP, although it enables OOP, which means it is possible to write OOP programs in C, but only very hard to do so. As such, if you have to write OOP code in C, there seems nothing wrong with using this approach, but it is preferable to use a language better suited for the purpose.

By trying to write OOP code in C, you have already entered a territory where "common sense" has to be overridden, so this approach is fine as long as you take responsibility to use it properly. You also need to ensure that it is thoroughly and rigourously documented and everyone concerned with the code is aware of it.

Edit Oh, you may have to use a cast to get around the const. I fail to recall if the C-style cast can be used like C++ const_cast.

share|improve this answer
Writing to an object which constness has been cast away is UB anyway. –  netcoder Dec 26 '12 at 17:11
Oh, I see. I seemed to recall having read that constness can be safely cast away, but I am perhaps mistaken. I will look that up again. Thanks. –  Happy Dec 26 '12 at 17:14
Ah I see the problem. I thought the question was about if this approach could be used. I overlooked the part about whether it is safe with other compilers than gcc. Sorry about that. –  Happy Dec 26 '12 at 17:19

You can use different approach - declare two structs, one for user without private members (in header) and one with private members for internal use in your implementation unit. All private members should be placed after public ones.

You always pass around the pointer to the struct and cast it to internal-use when needed, like this:

/* user code */
struct foo {
    int public;

int bar(void) {
    struct foo *foo = new_foo();
    foo->public = 10;

/* implementation */
struct foo_internal {
    int public;
    int private;

struct foo *new_foo(void) {
    struct foo_internal *foo == malloc(sizeof(*foo));
    foo->public = 1;
    foo->private = 2;
    return (struct foo*)foo;  // to suppress warning

C11 allows unnamed structure fields (GCC supports it some time), so in case of using GCC (or C11 compliant compiler) you can declare internal structure as:

struct foo_internal {
    struct foo;
    int private;

therefore no extra effort required to keep structure definitions in sync.

share|improve this answer
This does not implement the feature that “non-friends” of the class can read, but not write, members. –  Eric Postpischil Dec 27 '12 at 1:35
@EricPostpischil Agreed, but you can use setters/getters for it. I think it the closest one can get, therefore no reason to downvote –  qrdl Dec 27 '12 at 7:08
The question indicates accessors are to be avoided: “I hate using accessor when I don't need one.” This answer does not solve the problem posed. –  Eric Postpischil Dec 27 '12 at 14:29
@EricPostpischil My answer doesn't solve the problem indeed, but this problem cannot be solved, therefore I've offered the closest possible solution –  qrdl Dec 27 '12 at 15:01

Your Answer


