-2

I was studying object oriented C programming (how to implement virtual tables) and I saw some castings to do initialization that I would like to know if:

1 - Is there any undefined behaviors?

2 - Is this code portable?

3 - Is it also valid (well defined and portable) in C++?

In the actual code that I was studying, instead of simple data members like here, there were function pointers to constructors, destructors and clone functions, but I'm only interested if these kind of castings are well defined.

This code compiled and run as expected with gcc and g++.

struct Point
{
        int x;
        int y;
};

struct Square
{
        struct Point *topLeft;
        struct Point *bottomRight;
        int area;
};

int main()
{
        void *square = calloc(1,sizeof(struct Square));
        * (struct Point **) square = (struct Point *) calloc(1,sizeof(struct Point));
        * ( ( (struct Point **) square) + 1) = (struct Point *) calloc(1,sizeof(struct Point));

        struct Square *sqrptr = (struct Square *) square;
        sqrptr->topLeft->x = 2;
        sqrptr->topLeft->y = 3;
        sqrptr->bottomRight->x = 5;
        sqrptr->bottomRight->y = 7;
        sqrptr->area = 20;

        printf("Values: %d %d %d %d\n", (** (struct Point **) square).x,
                                        (** (struct Point **) square).y,
                                        (** ( ( (struct Point **) square) + 1) ).x,
                                        (** ( ( (struct Point **) square) + 1) ).y );

        free(sqrptr->topLeft);
        free(sqrptr->bottomRight);
        free(sqrptr);
}

Also, according to valgrind, there is no memory leaks.

EDIT: I just tried using C++ style castings, g++ doesn't give any error nor warning messages.

  • 1
    `void *square` should be `struct Square *square` – ikegami Jul 27 '19 at 21:47
  • 1
    `* (struct Point **) square` should be `square->topLeft` – ikegami Jul 27 '19 at 21:47
  • 1
    `* ( ( (struct Point **) square) + 1)` should be `square->bottomRight` – ikegami Jul 27 '19 at 21:47
  • 2
    Then you have much cleaner code with no casts needed. – ikegami Jul 27 '19 at 21:48
  • 1
    If you didn't cast anything, what errors does the compiler give you? That is what you should focus on, as the code you have now does really nothing except to tell the compiler to "shut up, I know what I'm doing", totally bypassing the type-safetyness of C++. – PaulMcKenzie Jul 27 '19 at 21:52
  • But this is the point of the question, are these void pointer castings ok? They need to be void pointers, because in the actual code they're implementing a new() function that initialize any kind of "classes", that have virtual tables. – chenriquecs Jul 27 '19 at 21:52
  • If you're compiling with C++, *remove the casts*. That will tell you right away if the casts are ok. The compiler's error list is what you should be using to determine if something is ok or not. No error, then it is ok -- Error, then inspect the error carefully. – PaulMcKenzie Jul 27 '19 at 21:53
  • In C++ implicit void * conversion is invalid, so the explicit conversion here is needed to compile with g++. – chenriquecs Jul 27 '19 at 21:55
  • In C++, it is preferred to use C++ casts, not C-style casts as your code is doing. Using C-style casts can cover up potential errors -- if you're going to cast, use C++ casts. Start with the error, and address the error with a C++ style cast, and a C++ cast only. If you give up and apply a C-style cast after exhausting all of the C++ alternatives, then suspect you're doing something fishy. – PaulMcKenzie Jul 27 '19 at 21:57
  • One could do that if one wanted to eliminate types and have the responsibility of accessing the proper data in one's own hands. I would argue that it's supposed to be easier with objects, not harder. (Maybe have a constructor?) – Neil Jul 27 '19 at 22:10
  • doing `+ 1` to access `bottomRight` from `topLeft` is definitely undefined behavior – kmdreko Jul 27 '19 at 22:27
  • PS - If you don't tag (e.g. `@ikegami`) a person, and they didn't write the post to on which you are commenting, they won't be notified of your comment. – ikegami Jul 28 '19 at 06:54
  • Note that structs can have padding between fields. Probably not going to be any between pointers at the start of the struct, but I don't know the details. Your code assumes there isn't any. – ikegami Jul 28 '19 at 06:56
  • Re "*They need to be void pointers, because in the actual code they're implementing a new() function that initialize any kind of "classes"*", This sounds like nonsense. For starters, a generic initializer makes no sense. Each class has its own initializer (constructor). For example, even with all the casts, your code makes specific assumptions that you have a `struct Square`. That's why you should be casting to the class or base class (`struct Square`), not away from it. My comments stand. – ikegami Jul 28 '19 at 06:58
  • @ikegami For study purposes, I want to make a function that does the same thing as the new keyword, so in the beginning of every single class, I would have a pointer to a VirtualTable struct with a function pointer to a initializer function void * (*ctor) (void * self), another function pointer to a clean up function void * (*dtor) (void *self) etc. My new() function would allocate memory to the class itself, and call the ctor inside the VirtualTable to do the initialization. So, will there be any padding before the very first field (the pointer to VirtualTable) of my structs? – chenriquecs Jul 28 '19 at 08:23
  • @ikegami If there is no undefined behavior in accessing the first field of all the structs (that would be a pointer to the struct VirtualTable), I would be able to achieve object oriented behavior in plain C. – chenriquecs Jul 28 '19 at 08:27
  • Create a base "class" (struct) with the VMT pointer. Make your subclasses unions of that type. – ikegami Jul 28 '19 at 09:25

1 Answers1

0

The piece of code that I posted has undefined behavior because of the access to the second data member in the structs:

* ( ( (struct Point **) square) + 1) = (struct Point *) calloc(1,sizeof(struct Point));

The compiler could align the Square struct so that the second data member would not start immediately after the first struct Point.

However, accessing the first data member is valid and well defined in both C and C++, because there is no padding before the first data member:

struct alignment C/C++

(C11, 6.7.2.1p15) "There may be unnamed padding within a structure object, but not at its beginning."

(C++11, 9.2p20) "There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment"

So, in the beginning of the structs I could put a pointer to a VirtualTable struct with function pointers to a initializer and clean up functions, and achieve objected oriented behavior in plain C. This would also be valid in C++.