0

I am trying to create a list struct in C, where there are 3 'elements' to the struct: the content, the datatype, and the pointer to the next element in the list. Here is the code:

struct listNode{
    void *content;
    char datatype;
    void *next;

};
typedef struct listNode listNode;

void printList(listNode *item){
    while (1){
        if (item->datatype == 'i'){
            printf("%d\n", item->content);
        } else if (item->datatype == 's'){
            printf("%s\n", item->content);
        } else if (item->datatype == 'c'){
            printf("%c\n", item->content);
        } else if (item->datatype == 'f'){
            printf("%f\n", item->content);
        }
        fflush(stdout);
        if (item->next != NULL){
            item = item->next;
        }
    }




}

int main(){

    listNode *element1;
    listNode *element2;
    listNode *element3;

    element1->content = (char*) "Hello World";
    element1->datatype = 's';
    element1->next = (struct listNode *) &element2;

    element2->content = (char*) 'z';
    element2->datatype = 'f';
    element2->next = (struct listNode *) &element3;

    element3->content = (int *) 5;
    element3->datatype = 'i';
    element3->next = (struct listNode *) NULL;
    printList(&element1);



    return 0;
}

When I run the code, I get 4 warnings, and three of them are the warning I have put in as the title. Here is what happens when I compile the code:

listc.c:17:19: warning: format specifies type 'int' but the argument has
      type 'void *' [-Wformat]
                        printf("%d\n", item->content);
                                ~~     ^~~~~~~~~~~~~
listc.c:21:19: warning: format specifies type 'int' but the argument has
      type 'void *' [-Wformat]
                        printf("%c\n", item->content);
                                ~~     ^~~~~~~~~~~~~
listc.c:23:19: warning: format specifies type 'double' but the argument has
      type 'void *' [-Wformat]
                        printf("%f\n", item->content);
                                ~~     ^~~~~~~~~~~~~
listc.c:52:12: warning: incompatible pointer types passing 'listNode **'
      (aka 'struct listNode **') to parameter of type 'listNode *' (aka
      'struct listNode *'); remove & [-Wincompatible-pointer-types]
        printList(&element1);
                  ^~~~~~~~~
listc.c:14:26: note: passing argument to parameter 'item' here
void printList(listNode *item){
                         ^
4 warnings generated.

When I run the code, I get the infamous Segmentation fault 11. Would someone please tell me how to fix the issue and all underlying problems. Also please excuse my terrible code as this is the first time I'm working with creating my own struct. Thanks!

Serket
  • 3,785
  • 3
  • 14
  • 45
  • ` &element2` is a `listNode **` and you cast it to `listNode *` – Ôrel Apr 23 '20 at 19:41
  • 1
    The last warning even tells you what to change – UnholySheep Apr 23 '20 at 19:41
  • In general to resolve segfualts you should use a debugger/valgrind/address sanitizer, to observe where the segfault occurs. See: https://stackoverflow.com/questions/29324796/how-to-debug-segmentation-fault – PiRocks Apr 23 '20 at 19:42
  • Also, what exactly are you trying to achieve with `element2->content = (char*) 'z';`? This cannot really work, you are casting a `char` to a `char*` – UnholySheep Apr 23 '20 at 19:43
  • Do you really want `content` to be a _pointer_? If so, you'd need (e.g.) `if (item->datatype == 'i') printf("%d\n",*(int *) item->content);` Or, do you want content to be _any_ type as in: `union { int c_int; char *c_str; int c_char; double c_float; } content;` Then, you'd need: `if (item->datatype == 'i') printf("%d\n",item->content.c_int);` – Craig Estey Apr 23 '20 at 19:45
  • @UnholySheep I am trying to make a 1 character long char pointer – Serket Apr 23 '20 at 19:46
  • If you really want a pointer, using the `union` may still be cleaner. `union { int *c_int; char *c_str; char *c_char; double *c_float; } content; And, `printf("%d\n",item->content->c_int);` – Craig Estey Apr 23 '20 at 20:07
  • Does this answer your question? [Correct format specifier to print pointer or address?](https://stackoverflow.com/questions/9053658/correct-format-specifier-to-print-pointer-or-address) – Top-Master Sep 13 '21 at 18:52

2 Answers2

0

You have to cast the type in printf function:

       if (item->datatype == 'i'){
            printf("%d\n", *(int *)item->content);
        } else if (item->datatype == 's'){
            printf("%s\n", (char *)item->content);
        } else if (item->datatype == 'c'){
            printf("%c\n", *(char *)item->content);
        } else if (item->datatype == 'f'){
            printf("%f\n", *(float *) item->content);
        }

And allocate for each element in main function:

    listNode *element1 = malloc(sizeof(listNode));
    if(!element1) {
       // handle erro 
    }
    listNode *element2 = malloc(sizeof(listNode));
    if(!element2) {
       // handle error 
    }
    listNode *element3 = malloc(sizeof(listNode));
    if(!element3) {
       // handle error 
    }

Then when you want to assign to string or int:

 element2->content = (char*) 'z';
 element3->content = (int *) 5;

change to (for example):

element2->content = "z"; // content is the string that content of 'z' and '\0' character.
// or
char ch = 'z';
element2->content = &ch; // content is pointer that points to character 'z'
int a = 5;
element3->content = &a; // that is pointer point to an integer value (5 in this case)

code for test:

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

struct listNode{
    void *content;
    char datatype;
   void *next;

};
typedef struct listNode listNode;

void printList(listNode *item){
    while (item){
        if (item->datatype == 'i'){
            printf("%d\n", *(int *)item->content);
        } else if (item->datatype == 's'){
            printf("%s\n", (char *)item->content);
        } else if (item->datatype == 'c'){
            printf("%c\n", * (char *)item->content);
        } else if (item->datatype == 'f'){
            printf("%f\n", * (float *) item->content);
        }

       item = item->next;

    }

}

int main(){

    listNode *element1 = malloc(sizeof(listNode));
    if(!element1) {
       // handle erro 
    }
    listNode *element2 = malloc(sizeof(listNode));
    if(!element2) {
       // handle error 
    }
    listNode *element3 = malloc(sizeof(listNode));
    if(!element3) {
       // handle error 
    }

    element1->content = (char*) "Hello World";
    element1->datatype = 's';
    element1->next = element2;

    char ch = 'z';
    element2->content = &ch;
    element2->datatype = 'c';
    element2->next = element3;

    int a = 5;
    element3->content = &a;
    element3->datatype = 'i';
    element3->next = NULL;
    printList(element1);

    free(element3);
    free(element2);
    free(element1);
    return 0;
}
Hitokiri
  • 3,607
  • 1
  • 9
  • 29
  • Why do I need to change it from the one-line declaration to the multi-line declaration (```element3->content = (int *) 5;``` to ```int a = 5; element3->content = &a;``` – Serket Apr 23 '20 at 20:47
  • because when you cast `(int *) 5`, it means you make the pointer `content` points to address `5`. You do not know this address is available to read or write. You can verify it by a simple test `int * content = (int *5); printf("addr a = %p, value = %d\n", a, *a);` then see what happens – Hitokiri Apr 23 '20 at 21:02
  • `&a` it means you make pointer point to address of variable `a`. The value of `a` is initialize by `5`, so the pointer `content` points to the address that has the value equal to `5` – Hitokiri Apr 23 '20 at 21:04
  • Thanks. The printList function is working now, but after it executes, and doesn't print the values stored in element2 and element3, I still get the Segmentation Fault 11, how can I fix this? – Serket Apr 23 '20 at 21:06
  • _```because when you cast (int *) 5, it means you make the pointer content points to address 5. You do not know this address is available to read or write.```_ then why can I declare a string pointer that way? – Serket Apr 23 '20 at 21:09
  • In C, there is not `string`. I call it by `string` because, it's easy to understand. "Hello World" is an array of many characters. So, we can assign `content` to it. – Hitokiri Apr 23 '20 at 21:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/212385/discussion-between-serket-and-hitokiri). – Serket Apr 23 '20 at 21:32
  • `int a = 5; element3->content = &a` is potentially dangerous code. If `a` is a local variable and the function returns, the value of `element3->content` will point to a non-existent variable. – Andrew Henle Apr 23 '20 at 22:54
  • @AndrewHenle this is definition in main function for testing the `printList` function, so there is not any dangerous here. – Hitokiri Apr 25 '20 at 15:19
0

If you want content to store different types of data, rather than point to objects of different types, then this is the wrong way to go about it. You'd be better off using a union type:

struct listNode{
    union {
      int i;
      char c;
      char *s;
      float f;
    } content;
    char datatype;
    void *next;
};    

You'd assign each element as

element1->content.s = "Hello World";
element1->datatype = 's';
element1->next = (struct listNode *) &element2;

element2->content.c = 'z';
element2->datatype = 'c';
element2->next = (struct listNode *) &element3;

element3->content.i = 5;
element3->datatype = 'i';
element3->next = (struct listNode *) NULL;

and then your output logic would be

if ( item->datatype == 'i' )
  printf( "%d\n", item->content.i );
else if ( item->datatype == 's' )
  printf( "%s\n", item->content.s );
else if ( item->datatype == 'c' )
  printf( "%c\n", item->content.c );
else if ( item->datatype == 'f' )
  printf( "%f\n", item->content.f );
...

A void * is used as a generic pointer type; it is not meant to store non-pointer values. It's not guaranteed that you can convert from the original type to the pointer type and back again and retrieve the original value. Its chief virtue is that you can assign any pointer type to a void * and vice versa without needing an explicit cast.

If you don't want to use a union, the other option is to keep content as a void * and dynamically allocate memory to store the contents of different non-pointer object types, and assign the address of that memory to content:

element1->content = "Hello, World"; // I'll explain this below
element1->datatype = 's';
element1->next = &element2;

element2->content = malloc( sizeof 'z' );  
if ( element2->content )
  *((int *)element2->content) = 'z'; // character constants have type int, not char!!    
element2->next = &element3;

element3->content = malloc( sizeof 5 );
if ( element3->content )
  *((int *)element3->content) = 5;
element3->next = NULL;

You're going to ask why I don't do a malloc for the "Hello, World" string, and it's because I don't need to. Expressions of array type, including string literals, "decay" to expressions of pointer type, and the value of the expression is the address of the first element of the array. In this case, I'm storing the address of the string literal to content. Since string literals exist over the lifetime of the program, this isn't a problem - that pointer will be valid throughout the lifetime of the program.

John Bode
  • 119,563
  • 19
  • 122
  • 198