Home > Mobile >  C Programming - How to properly declare a struct pointer in header file
C Programming - How to properly declare a struct pointer in header file

Time:09-13

I have three files: link_list_test.c, which has main() function. test_lib.h, and test-lib.c

I defined struct pointer in test_lib.h, and uses the same struct pointers in test_lib.c. However, it is not letting me compile.

The complain I get is

#  test_lib.c, on this line `first_node = malloc(sizeof(struct node_lib));`
conflicting types for 'first_node'; have 'int'
 
# test_lib.c, struct node_lib *first_node;
previous declaration of 'first_node' with type 'struct node_lib *'

# test_lib.c, if (first_node == NULL) {
expected identifier or '(' before 'if'

# test_lib.c, line first_node->value = 0;
expected '=', ',', ';', 'asm' or '__attribute__' before '->' token

# test_lib.c, first_node = malloc(sizeof(struct node_lib));
data definition has no type or storage class


# test_lib.c, first_node = malloc(sizeof(struct node_lib));
type defaults to 'int' in declaration of 'first_node' [-Wimplicit-int]

What does this mean?

I use VScode and GDB.

Content of test_lib.h:

#ifndef TEST_LIB_H
#define TEST_LIB_H

#include <stdio.h>

struct node_lib {
    int value;
    struct node_lib *next;
};
extern struct node_lib *first_node;
extern struct node_lib *current_node;
#endif

Content of test_lib.c

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

struct node_lib *first_node;

first_node = malloc(sizeof(struct node_lib));

if (first_node == NULL) {
    printf("Unable to allocate memory for new node_lib\n");
    exit(EXIT_FAILURE);
}

first_node->value = 0;

// every time calling create_new_node(new_value),
// expect first_node to append a new node with new_value.
void create_new_node(int value) {
    
    struct node_lib *new_list, *p;    

    new_list = malloc(sizeof(struct node_lib));

    if (new_list == NULL) {
        printf("Unable to allocate memory for new node_lib\n");
        exit(EXIT_FAILURE);
    }
    new_list->value = value;
    new_list->next = NULL;

    p = first_node;
    while (p->next != NULL) {
        p = p->next;
    }
    p->next = new_list;
    
   printf("%n\n", value);


}

Content of link_list_test.c

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

int main() {

    create_new_node(1);
    create_new_node(2);
    create_new_node(3);

    return 0;
}

CodePudding user response:

You cannot have code outside a function. Combined your 3 files into 1 as it's just easier to talk about. create_new_node() becomes a little more compact if you use a pointer to pointer (n) to where you want to insert a new node:

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

struct node_lib {
    int value;
    struct node_lib *next;
};
struct node_lib *first_node = NULL;

void create_new_node(int value) {
    struct node_lib **n;
    for(n = &first_node; (*n); n = &(*n)->next);
    *n = malloc(sizeof(**n));
    if(!*n) {
        printf("Unable to allocate memory for new node_lib\n");
        exit(EXIT_FAILURE);
    }
    (*n)->value = value;
    (*n)->next = NULL;
}

void print_nodes() {
    for(struct node_lib *n = first_node; n; n = n->next) {
        printf("value=%d\n", n->value);
    }
}

int main() {
    create_new_node(1);
    create_new_node(2);
    create_new_node(3);
    print_nodes();
    return 0;
}

and it prints:

1
2
3

CodePudding user response:

The first message compiling your code is

link_list_test.c(7,5): warning C4013: 'create_new_node' undefined; \
assuming extern returning int

And where are the lines of create_new_node()? Just below the floating lines

struct node_lib *first_node;

first_node = malloc(sizeof(struct node_lib));

if (first_node == NULL) {
    printf("Unable to allocate memory for new node_lib\n");
    exit(EXIT_FAILURE);
}

first_node->value = 0;

in link_list_test.c

as told in other answers you can not have this code outside functions. Wrapping those misplaced lines inside a function, let us say first_code()

void first_code()
{
    first_node = malloc(sizeof(struct node_lib));
    if (first_node == NULL)
    {
        printf(
            "Unable to allocate memory for new node_lib\n");
        exit(EXIT_FAILURE);
    }
    first_node->value = 0;
    return;
}

makes the compiler ok with the code and 2 errors remain:

test_lib.c(41,12): warning C4477: 'printf' : format string '%n' \
requires an argument of type 'int *', but \
 variadic argument 1 has type 'int'
test_lib.c(41,12): warning C4313: 'printf': '%n' in format string \
 conflicts with argument 1 of type 'int'

here

    printf("%n\n", value);

and by changing %n to the normal%d your code compiles ok.

I will go on a bit to write something that you may or may not find useful, so you can stop reading here.

A linked list is a data structure. A container of some stuff. In your case you are trying to build a list of int.

But a list is a collection of nodes and are the nodes that point to (or contain) a unit of the thing that is supposed to go into the list.

You should write the code in this way and your life will be easier: your next list can be the unavoiadable list of books, then the playlists, them some Person struct ;)

Also you shoud never use globals. Globals are a maintenance disaster and forbidden in may places, business and academy.

  • As a list is a dynamic thing consider using only pointers.
  • For help in testing write at first a function to navigate the list.
  • avoid extern as long as you could. This can lead you to many errors on linking programs. You do not need this here
  • always write your code the way you did: a header and the functions, and the main() program in a separate file. This way you can write many tests using 10s of main without changing the code in main. Never write all in a single file: testing is a mess and you will not be able to use the same code in other programs...

I will change your code as an example:

Here we can have the node and the list:

typedef struct st_node
{
    int             value;
    struct st_node* next;
}   Node;

typedef struct
{
    size_t size;
    Node*  start;
}   List;

And you see the list has a convenient size that is size_t, unsigned. And the list contains only a pointer to Node

making life easier: creating and destroying List

List* create();
List* destroy(List*);

Use pointers. Pointers are great in C. Also we will need

int   show(List*, const char*);
int   insert(int, List*);

So test_lib.h is now

#ifndef TEST_LIB_H
#define TEST_LIB_H
#include <stdio.h>

typedef struct st_node
{
    int             value;
    struct st_node* next;
}   Node;

typedef struct
{
    size_t size;
    Node*  start;
}   List;

List* create();
List* destroy(List*);
int   show(List*, const char*);
int   insert(int, List*);

#endif

implementing the 4 functions

create()

List* create()
{
    List* one = (List*) malloc(sizeof(List));
    if ( one == NULL ) return NULL;
    one->size = 0;
    one->start = NULL;
    return one;
};

It is easier to return a new List this way and each new List will have size 0 and pointers and whatever metadata you can need. A name maybe? Date of creation?

destroy()

Since we know the size it is just a loop.

List* destroy(List* L)
{  // here you write the code to
    // delete the `size` nodes and
    // free them
    if (L == NULL) return NULL;
    Node* p = L->start; // the 2nd or NULL
    Node* temp = NULL;
    for (size_t i = 0; i < L->size; i  = 1)
    {   // delete a node
        temp = p->next;
        free(p);
        p = temp;        
    }   // for
    free(L);
    return NULL;
}

Why return NULL? For safety. This code:

#include <stdio.h>
#include <stdlib.h>
#include "test_lib.h"
int main(void)
{
    List* one = create();
    show(one, "still empty");
    one = destroy(one);
    return 0;
}

Runs and shows

still empty
List: 0 elements

So you see that the pointer to the list can be set to NULL at the same line the list is destroyed: zero chances of using an invalid List pointer.

show()

Also simple, since each List has a size inside (encapsulation). And the msgis very convenient for test, as in the example above

int show(List* L, const char* msg)
{
    if (L == NULL) return -1;  // no list
    if (msg != NULL) printf("%s\n", msg);
    Node* p = L->start;  // can be NULL
    printf("List: %d elements\n", (int)L->size);
    for (size_t i = 0; i < L->size; i  = 1, p = p->next)
        printf("  %d", p->value);
    printf("\n"); // all in one line: just a test
    return 0;
}

Inserting at the end of the list insert_end()

This is very similar to show since we have only forward pointers

int insert_end(int value, List* l)
{   // the list has `size` elements
    // to insert at the end the last one
    // will point to this new one
    // the code is similar to show()
    if (l == NULL) return -1;  // no list
    Node* new  = (Node*)malloc(sizeof(Node));
    new->value = value;
    new->next  = NULL; // will be the last
    Node* p = l->start; 
    Node* last = NULL;
    for (size_t i = 0; i<l->size; i  = 1,p=p->next)
        last = p;
    // so `last` points to the last
    //  even if the list was empty
    l->size  = 1;
    if (l->size == 1)
    {   // ok: was empty
        l->start = new; // new start
        return 1;
    }
    last->next = new;
    return (int) l->size;
}

Insert at the start is easier insert_start()

int insert_start(int value, List* l)
{
    if (l == NULL) return -1; // no list
    Node* new = (Node*)malloc(sizeof(Node));
    new->value = value;
    new->next = NULL;
    // list may be empty
    if (l->size == 0)
    {
        l->start = new;
        l->size   = 1;
        return 1;
    }
    new->next = l->start;
    l->start = new;
    l->size  = 1;
    return (int) l->size;
}

a test: insert 1 to 5 at start 10 to 6 at the end

#include <stdio.h>
#include <stdlib.h>
#include "test_lib.h"
int main(void)
{
    List* one = create();
    show(one, "still empty");

    for (int i = 5; i >= 1; i -= 1) insert_start(i, one);
    show(one, "1 to 5");

    for (int i = 6; i <= 10; i  = 1) insert_end(i, one);
    show(one, "\n1 to 10 if ok...");
    one = destroy(one);
    return 0;
}

That shows

still empty
List: 0 elements

1 to 5
List: 5 elements
  1  2  3  4  5

1 to 10 if ok...
List: 10 elements
  1  2  3  4  5  6  7  8  9  10

If you are still reading you may find writing this way easier to read and maintain.

code for test_lib.c

This is the same as above but easier to copy if you want to test

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

List* create()
{
    List* one = (List*)malloc(sizeof(List));
    if (one == NULL) return NULL;
    one->size  = 0;
    one->start = NULL;
    return one;
}

List* destroy(List* L)
{  
    if (L == NULL) return NULL;
    Node* p = L->start; // the 2nd or NULL
    Node* temp = NULL;
    for (size_t i = 0; i < L->size; i  = 1)
    {   // delete a node
        temp = p->next;
        free(p);
        p = temp;        
    }   // for
    free(L);
    return NULL;
}

int show(List* L, const char* msg)
{
    if (L == NULL) return -1;  // no list
    if (msg != NULL) printf("%s\n", msg);
    Node* p = L->start;  // can be NULL
    printf("List: %d elements\n", (int)L->size);
    for (size_t i = 0; i < L->size; i  = 1)
    {
        printf("  %d", p->value);
        p = p->next;
    }
    printf("\n");  // all in one line: just a test
    return 0;
}

int insert_end(int value, List* l)
{   // the list has `size` elements
    // to insert at the end the last one
    // will point to this new one
    // the code is similar to show()
    if (l == NULL) return -1;  // no list
    Node* new  = (Node*)malloc(sizeof(Node));
    new->value = value;
    new->next  = NULL; // will be the last
    Node* p = l->start; 
    Node* last = NULL;
    for (size_t i = 0; i<l->size; i  = 1,p=p->next)
        last = p;
    // so `last` points to the last
    //  even if the list was empty
    l->size  = 1;
    if (l->size == 1)
    {   // ok: was empty
        l->start = new; // new start
        return 1;
    }
    last->next = new;
    return (int) l->size;
}

int insert_start(int value, List* l)
{
    if (l == NULL) return -1; // no list
    Node* new = (Node*)malloc(sizeof(Node));
    new->value = value;
    new->next = NULL;
    // list may be empty
    if (l->size == 0)
    {
        l->start = new;
        l->size   = 1;
        return 1;
    }
    new->next = l->start;
    l->start = new;
    l->size  = 1;
    return (int) l->size;
}
  • Related