4

After passing a void* pointer as argument to a function, is there a way to specify the type to which it is cast as another parameter. If I have two structs like:

struct A{
  int key;
  char c;
}

struct B { 
  int key;
  float d;
}

Is it possible to define a function,

void func(void * ptr, ...){
//operate on key
}

and pass a pointer to either structs to the function after casting to void* and access the key element from within the function.

Trying to understand the use of void*, how structure definitions are stored ( How are the offsets of various elements determined from the structure definition? ) and how ploymorphism may be implemented in c.

Was trying to see if I could write Binary Search tree functions that could deal with nodes of any struct.

Jeff
  • 43
  • 5
  • 3
    C doesn't pass anything in a void pointer except a bare memory address, so you would have to pass another parameter in your function with some type information, so that you could know what to cast back to. – Robert Harvey Feb 09 '23 at 14:26
  • @RobertHarvey not necessarily, if the first field is always `int key` operating on `key` should be fine provided that `ptr` points to a struct with the first field being an `int`. – Jabberwocky Feb 09 '23 at 14:28
  • 1
    @Jabberwocky That's not guaranteed unless you have a union containing both structs. – dbush Feb 09 '23 at 14:28
  • @dbush OK, I misread something then, thanks. Removing my comments. – Jabberwocky Feb 09 '23 at 14:29
  • 1
    @dbush on a second thought I'm not sure anymore. What about this answer: https://stackoverflow.com/a/16482997/898348 – Jabberwocky Feb 09 '23 at 14:30
  • 1
    @dbush and what about this: https://stackoverflow.com/questions/7312555/in-c-does-a-pointer-to-a-structure-always-point-to-its-first-member, which is most likely an exact duplicate. – Jabberwocky Feb 09 '23 at 14:37
  • 2
    Here’s a good read: [Struct Inheritance in C](https://stackoverflow.com/questions/1114349/struct-inheritance-in-c) – Dúthomhas Feb 09 '23 at 14:41
  • 2
    @Jabberwocky Pedantically, a good answer ought to also mention the "common initial sequence" rule. Since otherwise we can only inspect the first member of the struct, but not trailing members, even though they may be identical. – Lundin Feb 09 '23 at 14:41
  • So this should be dupe closed for https://stackoverflow.com/questions/7312555/in-c-does-a-pointer-to-a-structure-always-point-to-its-first-member. Anyone disagrees? – Jabberwocky Feb 09 '23 at 14:44
  • @Jabberwocky It's *probably* ok given that `ptr` has type `void *`, but it still seems hairy to me. This is how sockets functions taking a `struct sockaddr *` work which could actually point to a `struct sockaddr_un` or `struct sockaddr_in`, but of course that's considered part of the implementation. – dbush Feb 09 '23 at 14:44
  • 1
    @TomKarzes You are mixing up the guarantee that C makes of casting a struct pointer to a pointer to its _first_ member, with the common initial sequence rule, which states that two structs may be inspected even beyond the first member, but only if they are both placed in a union visible in the same translation unit. – Lundin Feb 09 '23 at 14:52
  • The first member could however be an inner struct, which in turn contains a bunch of members and then you don't need to bother about common initial sequence for the outer struct. – Lundin Feb 09 '23 at 14:53
  • 1
    BTW: _"...after casting to void*..."_: the cast to `void*` not necessary. – Jabberwocky Feb 09 '23 at 14:55
  • @Lundin If that's true, then it's a restriction that a lot of old code, and probably a lot of new code, doesn't follow. But suppose you have two structs whose first 3 fields are the same, and suppose you have a pointer to one of them (but you don't know which). Could you cast that pointer to a union type that contains both struct types, then use that union pointer to access the 3 common fields? If that's allowed, then it would permit what I suggest, provided you cast your pointers and access the common fields through the union. – Tom Karzes Feb 09 '23 at 15:04
  • @TomKarzes There's a lot of questionable code out there for sure, only working because of quality of compiler implementation, not because of any guarantee made by the C standard. And yes in your scenario you should be able to cast the pointer to either of the 2 struct types or to the union type. The catch is that the union definition must be present in the same translation unit. Also I wouldn't trust compilers to do what they are supposed to in cases like this regardless of the C standard, so such code should be avoided. – Lundin Feb 09 '23 at 15:19
  • @Lundin It sounds like the one thing the union provides is knowledge of the possible structure types that might be accessed. Unfortunately, that isn't something the compiler can automatically construct, since in general it doesn't know what those structure types are. But it does sound workable, albeit a bit cumbersome. – Tom Karzes Feb 09 '23 at 15:21
  • Jeff, Given Let us say "Is it possible to define a function, ... and pass a pointer to either structs to the function ... and access the key element from within the function." is easily doable. Now what do you want to do with the `key` and the pointer passed to the function? The answer to that drives how to best approach this question. – chux - Reinstate Monica Feb 09 '23 at 18:07
  • Linux uses [container_of](https://stackoverflow.com/q/15832301/2472827) quite extensively; useful for multiple inheritance. – Neil Feb 11 '23 at 03:33

2 Answers2

4

After passing a void* pointer as argument to a function, is there a way to specify the type to which it is cast as another parameter.

Yes and no.

I suppose you're hoping for something specific to this purpose, such as a variable that conveys a type name that the function can somehow use to perform the cast. Something along the lines of a type parameter in a C++ template, or a Java generic method, for example. C does not have any such thing.

But of course, you can use an ordinary integer to convey a code representing which of several known-in-advance types to cast to. If you like, you can even use an enum to give those codes meaningful names. For example:

enum arg_type { STRUCT_A_TYPE, STRUCT_B_TYPE };

void func(void *ptr, enum arg_type type) {
    int key = 0;

    switch (type) {
        case STRUCT_A_TYPE:
            key = ((struct A *) ptr)->key;
            break;
        case STRUCT_B_TYPE:
            key = ((struct B *) ptr)->key;
            break;
        default:
            assert(0);
    }
    // ...
}

Note well that that approach allows accessing any member of the pointed-to structure, but if you only want to access the first member, and it has the same type in every structure type of interest, then you don't need to know the specific structure type. In that particular case, you can cast directly to the member type:

void func(void *ptr) {
    int key = *(int *)ptr;
    // ...
}

That relies on C's guarantee that a pointer to any structure, suitably cast, points to that structure's first member.

Trying to understand the use of void*, how structure definitions are store and how ploymorphism may be implemented in c.

That's awfully broad.

C does not offer polymorphism as a language feature, and C objects do not carry information about their type such as could be used to dispatch type-specific functions. You can, of course, implement that yourself, but it is non-trivial. Available approaches include, but are not limited to,

  • passing pointers to functions that do the right thing for the type of your data. The standard qsort() and bsearch() functions are the canonical examples of this approach.

  • putting some kind of descriptor object as the first member of every (structure) type. The type of that member can be a structure type itself, so it can convey arbitrarily complex data. Such as a vtable. As long as it is the first member of all your polymorphic structures, you can always access it from a pointer to one of them by casting to its type, as discussed above.

  • Using tagged unions of groups of polymorphic types (requiring that all the type alternatives in each group be known at build time). C then allows you to look at any members of the common initial sequence of all union members without knowing which member actually has a value. That initial sequence would ordinarily include the tag, so that you don't have to pass it separately, but it might include other information as well.

  • Polymorphism via (single-)inheritance can be implemented by giving each child type an object of its parent type as its first member. That then allows you to cast to (a pointer to) any supertype and get the right thing.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

Lets say you had a sort function that takes a function as a parameter which implements the "compare" functionality of the sort. The sort would then be capable of sorting a list of any arbitrary struct, by handing it a comparer function that implements the correct order for your particular struct.

void bubbleSort(Node* start, bool comparerFunction(void* a, void* b))

Consider the following struct definition:

typedef struct {
   int  book_id;
   char title[50];
   char author[50];
   char subject[100];
   char ISBN[13];
} Book;

And this unremarkable linked list definition:

typedef struct node{
  void* item;
  struct node* next;
} Node;

Which can store an arbitrary struct in the item member.

Because you know the type of the members you've placed in your linked list, you can write a comparer function that will do the right thing:

bool sortByTitle(void* left, void* right) {
  Book* a = (Book*)left;
  Book* b = (Book*)right;
 
  return strcmp(a->title, b->title) > 0;
}

And then call your sort like this:

bubbleSort(myList, sortByTitle);

For completeness, here is the bubbleSort implementation:

/* Bubble sort the given linked list */
void bubbleSort(Node *start, bool greaterThan(void* a, void* b)) 
{ 
    int swapped, i; 
    Node* ptr1; 
    Node* lptr = NULL; 

    /* Checking for empty list */
    if (start == NULL) 
        return; 

    do
    { 
        swapped = 0; 
        ptr1 = start; 

        while (ptr1->next != lptr) 
        { 
            if (greaterThan(ptr1->item, ptr1->next->item))
            { 
                swap(ptr1, ptr1->next); 
                swapped = 1; 
            } 
            ptr1 = ptr1->next; 
        } 
        lptr = ptr1; 
    } 
    while (swapped); 
} 

/* function to swap data of two nodes a and b*/
void swap(Node *a, Node *b) 
{ 
    void* temp = a->item; 
    a->item = b->item; 
    b->item = temp; 
} 
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501