1

A static library that can receive any struct via void * and is able to access it's member variables whether an embedded struct or a pointer to a function.

It's guaranteed that a member variable which is an embedded struct will be there and is accessible via super and a member variable which is a pointer to a function will be available called execute.

Question - (in short how to access underlying struct members using void * only)

I guess it's related to generic programming, can we somewhow access the following via memory addresses or any other alternate way. casting to specific structure is not available at compile time struct will sure have an execute pointer function and super variable. Guaranteed.

// from src/library.c (someStruct a void *, casting to specific struct is not available)
someStruct->execute();
someStruct->super->execute();

Attempt 1 Reference

struct Super *pointer = someStruct + 8; // works
pointer->execute(); // prints overridden method!

// how to access second member which is a pointer to function

Open to any form of solutions whether it's going low level to memory addresses to access each member or any other alternate way.

Project Download


Expected Output:

overridden method!

super method


src/library.h

#ifndef POLY_LIBRARY_H
#define POLY_LIBRARY_H

struct Library {
    void (*passSomeStruct)(void *someStruct);
};

struct Library *newLibrary();

#endif

src/library.c

#include "library.h"
#include <stdlib.h>

void passSomeStruct(void *someStruct) {

    // can we somewhow access the following via memory addresses or any other alternate way.
    // casting to specific structure is not available at compile time
    // struct will sure have an execute pointer function and super variable
      
      someStruct->execute();
      someStruct->super->execute();
}

struct Library *newLibrary() {
    struct Library *library = malloc(sizeof(struct Library));
    library->passSomeStruct = passSomeStruct;
    return library;
}

src/super.h

#ifndef POLY_SUPER_H
#define POLY_SUPER_H

struct Super {
    void (*execute)();
};

struct Super *newSuper();

#endif //POLY_SUPER_H

src/super.c

#include "super.h"
#include <stdio.h>
#include <stdlib.h>

static void execute() {
    printf("super method\n");
}

struct Super *newSuper() {
    struct Super *super = malloc(sizeof(struct Super));
    super->execute = execute;
    return super;
}

Test

test/library_test.h

#ifndef POLY_LIBRARY_TEST_H
#define POLY_LIBRARY_TEST_H

#endif //POLY_LIBRARY_TEST_H

test/library_test.c

#include "../src/library.h"
#include "temp.h"

int main() {
    struct Library *library = newLibrary();
    struct Temp *temp = newTemp();
    library->passSomeStruct(temp);
    return 0;
}

test/temp.h

#ifndef TEST_SUPER_H
#define TEST_SUPER_H

#include <stdlib.h>

struct Temp {
    struct Super *super;
    void (*execute)();
};

struct Temp *newTemp();

#endif //TEST_SUPER_H

test/temp.c

#include "temp.h"
#include <stdio.h>
#include "../src/super.h"

static void execute() {
    printf("overridden method!\n");
}

struct Temp *newTemp() {
    struct Temp *temp = malloc(sizeof(struct Temp));
    temp->super = newSuper();
    temp->execute = execute;
    return temp;
}

MakeFile

VPATH := src test

all: libTest

libTest: super.o library.o
    mkdir -p bin
    ar rcs bin/libTest $^

test: library_test
    ./library_test

library_test: library_test.o library.o super.o temp.o
    $(CC) -o $@ $^

%.o: %.c %.h
Community
  • 1
  • 1
App2015
  • 973
  • 1
  • 7
  • 19
  • 1
    I'm confused, are you asking a question? – Pablo Jan 30 '18 at 00:49
  • yes, see the question section – App2015 Jan 30 '18 at 00:50
  • The whole post is **a** question, what section are you talking about? The code comments in `void passSomeStruct(void *someStruct)`? – Pablo Jan 30 '18 at 00:51
  • yes and I've added a section titled question as well. – App2015 Jan 30 '18 at 00:53
  • So, you are trying to do OOP in C, right? There is this [book](http://www.cs.rit.edu/~ats/books/ooc.pdf) by Axel Schreiner that goes deep in detail about this topic. I also like how the glib library solves this Problem with [GObject](https://en.wikibooks.org/wiki/C_Programming/GObject). I think your question is just to broad. – Pablo Jan 30 '18 at 00:57
  • I guess the gist is how to access underlying member variables or function pointers of a struct through `void *` without casting. – App2015 Jan 30 '18 at 00:59
  • yes I did go through the book but I still need help, I couldn't find a section on overriding method and calling super unless you can point or read the above comment about the gist. you may also download the project and execute yourself to understand. Run `make` and `make test` – App2015 Jan 30 '18 at 01:02
  • the question is not broad, I've done 90% of it, it's this 10% that deals with handling `void *`, can you access members of a struct through a `void *`? that's all there's to the question. Don't let the length of the code scare you. – App2015 Jan 30 '18 at 01:06
  • If you only have a `void` pointer and you don't have the real structure that you can cast the `void` pointer, I'm afraid you cannot do that. A possible way would be that you cast the `void` pointer to `char*`. If you know the byte layout that the compiler uses for your target instruction, then you could access the bytes directly through the casted `char*` pointer. But that would be portable at all. – Pablo Jan 30 '18 at 01:14
  • yes, that's the part I need to know more, I don't know much about that domain, byte layout, casting to char* etc. At minimum it's guaranteed that each `void *` will have two members, one an embedded struct (real structure available and it's `struct Super` defined in super.h) and a pointer function (signature known, returns void and accepts void). Can you please write some code in `passSomeStruct` to do all that? I guess this part they call generic programming in c – App2015 Jan 30 '18 at 01:19
  • and I'd also appreciate if you can provide some link that can help me understand more about programming using plain memory addresses – App2015 Jan 30 '18 at 01:34
  • the C language does not have `polymorphism` nor `inheritance`, Perhaps your confusing with C++ – user3629249 Jan 30 '18 at 01:59
  • I know, that's why it says simulating, anyway the gist of the question is to access underlying members via `(void *)` without casting, can you do that, see `passSomeStruct` in library.c. If you can do that then polymorphism and inheritance can be simulated in C. – App2015 Jan 30 '18 at 02:01
  • @Pablo see the section Attempt 1 please in my question. – App2015 Jan 30 '18 at 02:31
  • @Pablo see my answer below, is this is what you were suggesting? – App2015 Jan 30 '18 at 02:55
  • @App2015 more or less yes, but this is very fragile code, change a tiny bit in the structs and the whole thing blows up. – Pablo Jan 30 '18 at 03:04

2 Answers2

1

I'm afraid that I cannot give you a perfect example of how OOP should be done in C, not of the top of my head in a couple of minutes anyway. There are other threads here in Stack Overflow that cover this is in far more detail, like this one:

Can you write object-oriented code in C?

I highly recommend that you read that thread, there are many good answers there.

I also recommend, that if you really want to use OOP in your code base, don't write it yourself, it is very complex and would be very time consuming. In that case I would recommend that you use GObject that is part of the code of the glib library. I tell you all this because in the comments you say

yes, that's the part I need to know more, I don't know much about that domain, byte layout, casting to char* etc.

and when it come to stuff like this, knowing these things is fundamental.

I'm going to give you a very brief example of what I meant in the comments about the memory layout, etc.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

#define TEND 0
#define TINT 1
#define TSTRING 2
#define TDOUBLE 3

typedef struct typeinfo {
    int type;
    size_t offset;
} TypeInfo;




typedef struct K1 {
    int a;
    int b;
    char c[32];
} K1;

void print_K1(K1 *obj)
{
    printf("K1: %d %s %d\n", obj->a, obj->c, obj->b);
}

TypeInfo *init_k1(K1 *obj, int a, int b, const char *c)
{
    TypeInfo *types = calloc(4, sizeof *types);

    obj->a = a;
    obj->b = b;
    strcpy(obj->c, c);

    // filling type info
    // we know the structure, we can provide this information
    types[0].type = TINT;
    types[0].offset = offsetof(K1, a);

    types[1].type = TINT;
    types[1].offset = offsetof(K1, b);

    types[2].type = TSTRING;
    types[2].offset = offsetof(K1, c);

    types[3].type = TEND;

    return types;
}

typedef struct K2 {
    int k;
    double o;
} K2;

void print_K2(K2 *obj)
{
    printf("K2: %d %lf\n", obj->k, obj->o);
}

TypeInfo *init_k2(K2 *obj, int k, double o)
{
    TypeInfo *types = calloc(3, sizeof *types);

    obj->k = k;
    obj->o = o;

    // filling type info
    // we know the structure, we can provide this information
    types[0].type = TINT;
    types[0].offset = offsetof(K2, k);

    types[1].type = TDOUBLE;
    types[1].offset = offsetof(K2, o);

    types[2].type = TEND;

    return types;
}



void somefunc(void *ptr, TypeInfo *types)
{
    char *base = ptr, *offset;

    while(1)
    {
        offset = base + types->offset;
        switch(types->type)
        {
            case TINT:
            {
                int *p = (int*) offset;
                *p *= -1;
                break;
            }

            case TSTRING:
            {
                char *p = offset;
                p[0] = ' ';
                break;
            }

            case TDOUBLE:
            {
                double *p = (double*) offset;
                *p *= 0.35;
            }

            case TEND:
                return;
        }

        types++;
    }
}

int main(void)
{
    K1 k1;
    K2 k2;

    TypeInfo *t1, *t2;

    t1 = init_k1(&k1, 33, -23, "Hello Peter");
    print_K1(&k1);
    somefunc(&k1, t1);
    print_K1(&k1);

    t2 = init_k2(&k2, 112, 122.12);
    print_K2(&k2);
    somefunc(&k2, t2);
    print_K2(&k2);

    free(t1);
    free(t2);

    return 0;
}

I have two struct K1 and K2 with different members, that means their memory layout is different. For K1 and K2 there are init functions that using the helper struct TypeInfo store the offsets and types of the struct members of their own class. This information is known to them, so they can construct such a table of information. offsetof returns the offset of the field member from the start of the structure type.

The function somefunc doesn't know anything about the structs, so it cannot cast the void pointer to a pointer of the structs. However it gets a list of types from the caller. This functions changes the sign for every int member, replaces the first letter with an empty character for every string and multiplies every double by 0.35. Thanks to types it can get the members by using the offset supplied by types.

The main function initializes one object of each struct, prints the original state, calls somefunc with the object and the type information and prints the objects again. In the output you can see that the values were change accordingly.

Output

K1: 33 Hello Peter -23
K1: -33  ello Peter 23
K2: 112 122.120000
K2: -112 42.742000

While this might work, I don't think that this is an elegant solution. This is a basic example but it could get very complicated when dealing with more complex structs. This shows you how you can access the contents of a struct where you only have a void pointer. But without the type information, this could not be done. For that reason I encourge you to use some library that has already implemented a typing system. GObject is very mature and widely use in GTK+ applications. The Glib and GTK+ folks have been using that for years, so I would trust their implementation over any self implemented solution.

Pablo
  • 13,271
  • 4
  • 39
  • 59
0

Finally I got it demystified, but I'm open to suggestions, can anyone improve this answer and make it better or explain to me what's going on in here?

It's some sort of magic number 8, that did the trick.

void passSomeStruct(void *someStruct) {
    struct Super *pointer = someStruct + 8; 
    pointer->execute(); // outputs "overridden method!"

    pointer = someStruct + 16; 
    pointer->execute(); // outputs "super method"
}

Edit

my struct definition, I'm wondering why super which is defined first is 16 offset and the method execute I defined is 8 offset, is memory allocated is in reverse and not in the order I defined them?

struct Temp {
    struct Super *super;
    void (*execute)();
};
App2015
  • 973
  • 1
  • 7
  • 19
  • The "magic" 8 is the offset of where you `struct Super` is in memory in regards to the containing struct. The problem of this "magic" number is that if you later change the structure, for whatever reason, the offset might change and `passSomeStruct` becomes garbage. Read my answer where I explain that and give you recommendations of what you should use in a production project. Unless you do it for fun. – Pablo Jan 30 '18 at 03:03
  • @Pablo does order matter? See my edit section for this answer please. – App2015 Jan 30 '18 at 03:31
  • The order of the elements of the struct? Of course it does, the offsets are dependent on the order of the elements of the struct. That's what I meant with "change a little and the whole thing blows up". You change the order for some reason and the "magic" numbers become invalid. Look at the code in my answer, there you can change the order without having problems. – Pablo Jan 30 '18 at 10:53