0

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;
}
6
  • 5
    Code needs to be inside a function. Commented Sep 13, 2022 at 0:54
  • 1
    first_node = malloc(sizeof(struct node_lib)); is a statement (an expression statement). Statements can only be inside functions. The compiler is attempting to parse it as a declaration, and that leads to the error message (in combination with the archaic behavior of defaulting a type to int). This is a duplicate of previous questions. Commented Sep 13, 2022 at 0:57
  • @RetiredNinja: Declarations are code and do not need to be inside functions. Statements need to be inside functions. Commented Sep 13, 2022 at 0:57
  • Unrelated: Learn to use typedef and save yourself a mountain of typing (and readers a mountain of reading.) Further, the parameter to malloc() is "better" if it is sizeof the object you hope to point to. Eg: new_list = malloc( sizeof *new_list ); This saves, even, typing another pair of parentheses and reduces the chance of creating a bug by careless future editing not changing two instances of naming the struct. Commented Sep 13, 2022 at 1:08
  • If you wouldn't mind posting the entire error message, that'd be fantastic... Commented Sep 13, 2022 at 1:18

2 Answers 2

1

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_end(int, List*);
int   insert_start(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;
}
Sign up to request clarification or add additional context in comments.

2 Comments

I truly appreciate this very detailed answer. There's a lot of information packed in it, I'll take some time to digest them. From the destroy() function, it looks like to free a linked list, each single node needs to be freed, not just the top node, is that right?
yes. It is true for all these containers. You free() the structs in the reverse order they had been malloced: first the nodes them the list itself
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.