Implementing a simple linked list in C without malloc - c

All the implementations I have seen online use pointer to declare nodes and then will use malloc to create space for them like this:
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node* head = NULL;
struct Node* second = NULL;
struct Node* third = NULL;
head = (struct Node*)malloc(sizeof(struct Node));
second = (struct Node*)malloc(sizeof(struct Node));
third = (struct Node*)malloc(sizeof(struct Node));
...
But I can also create the same without pointers and malloc like this:
struct node {
int id;
struct node* next;
};
struct node head = {0, NULL};
struct node one = {1, NULL};
struct node two = {2, NULL};
struct node tail = {3, NULL};
int main(){
head.next = &one;
one.next = &two;
two.next = &tail;
...
My question is, why the 1st method is mostly the one used, why do we need to declare each node as pointer and why do we need malloc?
(Just to point out I know why struct Node *next; is declared as pointer int the struct declaration).

You should do this with local variables, not global ones, but the general idea would be the same. You should also steer towards having arrays and not heaps of otherwise unrelated variables:
struct node {
int id;
struct node* next;
};
int main(){
struct node nodes[4];
for (int i = 0; i < 4; ++i) {
nodes[i].id = (3 - i);
if (i != 0) {
nodes[i].next = &nodes[i-1];
}
}
return 0;
}
Where something like that assembles them in reverse order for convenience, but they're all grouped together in terms of memory initially.
malloc is used because you often don't know how many you're going to have, or they get added and removed unpredictably. A general-purpose solution would allocate them as necessary. A specialized implementation might allocate them as a single block, but that's highly situational.
Here the lifespan of nodes is within that function alone, so as soon as that function ends the data goes away. In other words:
struct node* allocateNodes() {
struct node nodes[10];
return &nodes; // Returns a pointer to an out-of-scope variable, undefined behaviour
}
That won't work. You need a longer lived allocation which is precisely what malloc provides:
struct node* allocateNodes() {
struct node *nodes = calloc(10, sizeof(struct node));
return nodes; // Returns a pointer to an allocated structure, which is fine
}
The problem is if you malloc you are responsible for calling free to release the memory, so it becomes more work.
You'll see both styles used in code depending on the required lifespan of the variables in question.

If you know ahead of time exactly how many items will be in your list, then you're probably better off using an array rather than a list. The whole point of a linked list is to be able to grow to an unknown size at runtime, and that requires dynamic memory allocation (i.e., malloc).

Related

I am using malloc() to allocate a pointer (8bytes) but it's able to store the whole node (16bytes). How?

What I did :
I added a pointer in the parameter of malloc(struct Node *) which is usually malloc(struct Node).
Problem :
When I am allocating memory for the size of a pointer then how the code is working?
malloc() is allocating 8 bytes of memory and returning the pointer
So the pointer points to an 8 bytes memory block which but further I am storing more data in it.
struct Node
{
int data;
struct Node *next;
};
struct Node *GetNode(int data)
{
struct Node *node = (struct Node *)malloc(sizeof(struct Node *));
node->data = data;
node->next = NULL;
return node;
}
Difference in size:
I know that malloc is allocating different sizes because I did this
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *next;
};
void main(){
int i = sizeof(struct Node);
int j = sizeof(struct Node *);
printf("%d,%d", i, j);
}
Output
16,8
What you've done is undefined behavior. It could work (on some systems, on certain days of the week), or it could crash, or it could produce incorrect results in seemingly unrelated calculations.
Imagine you rent a garage space for your truck, but to save money you request a space for a car. What happens when you drive your truck in? Maybe it fits, maybe you wreck the truck, maybe you destroy the garage.
So don't do that, and to help you find cases where you've made similar mistakes, build with Address Sanitizer. It will log when you access memory in an invalid way.
UB is a fickle mistress. Sometimes agreeable; sometimes not.
That is why you should use a syntax that makes it less likely to make mistakes:
struct Node *node = (struct Node *)malloc(sizeof(struct Node *)); // bad
struct Node *node = malloc( sizeof *node ); // good
No casting, and no redundancy.
I would even recommend using typedef's to free yourself from "struct, struct, struct".
typedef struct s_node
{
int data; // use more indentation, please. whitespace is cheap.
struct s_node *next;
} Node_t;
/* ... */
Node_t *node = malloc( sizeof *node ); // better
Finally, a lot of hours are wasted tracking down bugs that appear and disappear. Until performance becomes a real issue that you understand, I'd recommend using calloc() as the go-to allocation function:
Node_t *node = calloc( 1, sizeof *node ); // best
You can sleep easier knowing the byte values will be repeatable from run to run.

Pointers to Structs Implementation of Stack Data Structure in C

I've created a Stack structure in C. When the stack is initialized with a value, I am able to print it back and receive the correct output. However, after pushing a new string, the print function prints what appears to be a random character (ASCII 177).
Originally, I implemented this project with pointers, but I was unable to get it working. Instead, I opted to only use a pointer for the Node *nodes member of Stack. That way, when I need to grow the stack, I can just multiply the amount of memory required by Stack.size. However, this approach has not worked yet either.
#define MAX_DATA 64
struct Node{
char val[MAX_DATA];
};
struct Stack{
int size;
struct Node *nodes;
};
These are used as follows:
struct Node node = {.val = "Test"};
struct Stack stack = newStack(node);
printStack(stack);
The newStack function initializes nodes properly. For the sake of inclusion:
struct Stack newStack(struct Node node)
{
struct Stack stack;
stack.size = 1;
stack.nodes = (struct Node*) malloc(sizeof(struct Node));
stack.nodes[0] = node;
return stack;
}
The stack is then printed iteratively in printStack(), with stack.size being the upper bound of the for-loop.
The trouble comes when I try to run:
struct Node node2 = {.val = "Test1"};
push(stack, node2);
printStack(stack);
The push function aims to create a temporary stack and assign the value of the stack to it. After this, the size is incremented, the pointer to the nodes is freed, and new memory is allocated, with room for a new member at the end.
void push(struct Stack stack, struct Node node)
{
struct Stack temp_stack = stack;
stack.size += 1;
free(stack.nodes);
stack.nodes = (struct Node*) malloc(sizeof(struct Node) * stack.size);
for(int i = 0; i < temp_stack.size; i++){
stack.nodes[i] = temp_stack.nodes[i];
}
stack.nodes[stack.size - 1] = node;
}
Needless to say, this doesn't execute properly.
The expected output would be:
Test
Test1
But, instead, I receive only ASCII-177. It is also worth noting that the execution hangs after it prints that and moves to the new line. This results in Aborted (core dumped).
Am I improperly freeing and re-allocating memory? Any help would be appreciated. Thank you in advance!
It's worth remembering that in C, passing a struct to a function is pass-by-value, i.e., the function gets a copy of the struct. All members of the struct, including pointer variables (but not whatever the pointer references), are duplicated.
So in the push function, think about what happens to the original when you modify this copy (e.g., stack.size += 1, and also when you free() the stack.nodes.
Thank you to peekay for the recommendations. I ended up doing a re-write. It became quite a bit simpler. I did remove a layer of "visibility" by storing the nodes as a pointer, but I suppose this implementation is a bit more true to the data structure.
Node is implemented as a means to hold data, and the user doesn't interact with it directly. It also points to the following Node. Stack is implemented to hold the top Node, and also the size of the Stack.
Node:
struct Node{
char val[MAX_DATA];
struct Node *next;
};
Stack:
struct Stack{
struct Node *top;
int size;
};
Push:
void push(struct Stack *stack, char *newVal)
{
struct Node *newNode;
newNode = (struct Node*) malloc(sizeof(struct Node));
strcpy(newNode->val, newVal);
newNode->next = stack->top;
stack->top = newNode;
stack->size++;
}
Usage:
struct Stack stack;
newStack(&stack);
push(&stack, "Test");
push(&stack, "Test1");
push(&stack, "Test2");
push(&stack, "Test3");
Complete Code
Updated, accessible Nodes

Hide type's nature

To learn linked lists' (doubly-linked) conception I'm writing a bunch of simple common functions to handle them. Most of the functions take pointer to the DL_List
structure, which is the handle and contains not only links to the first and last elements but also current position in the list.
typedef struct DL_Node {
struct DL_Node *next = NULL;
struct DL_Node *prev = NULL;
int data;
} DL_Node;
typedef struct DL_List {
struct DL_Node *first = NULL;
struct DL_Node *last = NULL;
struct DL_Node *cur = NULL;
} DL_List;
Therefore I always have to pass a pointer to the handle.
int main() {...
DL_List list; // lives on the stack
init_list(&list, 9);
push(&list, 7);
append(&list, 10);
insert_after(&list, -31);
print_list(&list);
...}
So here is the question: is there any way to avoid rereading passing &list? Maybe typedefing?
github repo
You can do
int main() {...
DL_List list; // lives on the stack
DL_List * plist = &list;
init_list(plist, 9);
push(plist, 7);
append(plist, 10);
insert_after(plist, -31);
print_list(plist);
...}
if you want to avoid typing &, but it doesn't really save you something.
A good compiler should optimize this away.
Short of using global variables or horrendous macros, no. You explicitly pass as arguments the objects you want a function to operate on.

Allocating space to a node pointer

I am currently attempting to use a doubly linked list to sort some data. I am having trouble creating a new node with the given data. Below was the code given to me:
#ifndef LIST_H_
#define List_H_
#define MAX_SYMBOL_LENGTH 7
struct order {
int id;
char symbol[MAX_SYMBOL_LENGTH];
char side;
int quantity;
double price;
};
typedef struct order* OrderPtr;
typedef struct onode* NodePtr;
struct onode {
OrderPtr data;
NodePtr next;
NodePtr prev;
};
This is the code that I have written using list.h as a header.
Here is the code that seemingly keeps crashing:
#include "list.h"
NodePtr newNode(OrderPtr data){
NodePtr node = (NodePtr)malloc(sizeof(NodePtr));
//node->data = (NodePtr)malloc(sizeof(OrderPtr));
//*node->data = *data;
node->data = data;//This is the one I am having problems with
node->next = NULL;
node->prev = NULL;
return node;
}
It compiles fine but when I try and submit it to an online grader it says that it does not work.
Here is my thought process,
create memory for NodePtr.
create memory for NodePtr->data.
and then assign the values of data passed from the function to the values in Node->Ptr.
But I do not know how to allocate memory for NodePtr->data.
NodePtr node = (NodePtr)malloc(sizeof(NodePtr));
Isn't doing what you are thinking. It's allocate space to hold a pointer same as sizeof(int*), it's 4 bytes on 32-bit machine, usually.
You need to do NodePtr node = malloc(sizeof(struct onode)); instead of.
data member should be result to a malloc(sizeof(struct order));
Also, don't cast result value from a malloc() call.
NodePtr is a pointer to a node and not the node itself. You're only allocating enough memory for a pointer and not all the members of the onode structure. You'll want to call malloc with sizeof(struct onode).

type-problems in C

This may be a stupid question, and I see similar questions been asked, but I dont get the answers given. why does the following code produce:
error: incompatible types when assigning to type ‘node_t’ from type ‘struct node_t *’
node_t list_array[10];
typedef struct node
{
int value;
struct node *next;
struct node *prev;
} node_t;
node_t* create_node(void)
{
node_t *np;
np->next = NULL;
np->prev = NULL;
np->value = rand() % 10;
return np;
}
int main(void)
{
int i;
for(i = 0; i < 10; i++)
{
list_array[i] = create_node();
}
return 0;
}
Make the array into an array of pointers to fix the error, since create_node returns a pointer:
node_t *list_array[10];
Note you're not allocating any memory in create_node so using np is illegal. Try:
node_t *np = malloc(sizeof *np);
I want to make an array of node_t structs
In that case you could leave the node_t list_array[10] and:
Pass &list_array[i] as an argument to the function
Have the function return a node_t instead of a node_t *
Because one is a structure and the other is a pointer to the structure.
The create_node() function returns a pointer to a node (which you really should malloc() in that function, by the way) and you try to assign it to an actual structure in the array.
You can solve it by simply changing your declaration to:
node_t *list_array[10];
so that it's an array of pointers rather than an array of structures.
Because create_node() returns a pointer, but list_array[i] is an actual instance. You can't assign a pointer over an instance, they're completely different.
The solution is typically to represent each node as a pointer, which requires list_array to be an array of pointers:
node_t *list_array[10];
Then the assigment makes sense, and the code will compile.
Note, however, that the code will not "work", since it's dereferencing a NULL pointer inside create_node(). It seems you forgot to call malloc():
node_t* create_node(void)
{
node_t *np;
if((np = malloc(sizeof *np)) != NULL)
{
np->next = NULL;
np->prev = NULL;
np->value = rand() % 10;
}
return np;
}
This is the classic "pointer vs. instance" confusion. Even more serious than your warning is:
node_t *np;
np->next = NULL;
which will compile, and then segfault.
The confusion arises because of a misunderstanding of what a pointer is. When compiled, a pointer is just a single number, like 140734799803888. This number is used just to locate the physical chunk of data. It's a memory address.
Pointers vs. instances are confusing, and one of the first conceptual challenges you encounter in programming. So here's an analogy:
If you've ever used a GPS, it will tell you where you are (pointer) but not what you are (data). Pointers work the same way. If someone wants to shake your hand, they wouldn't shake GPS coordinates (pointer)! They'd use the GPS coordinates to locate you, then physically visit you (data) and shake your hand. That's how pointers work.
So in your code above, you declare a pointer np, but don't give it any location to keep track of. Then, you ask "use the number in np to locate my data" (but you haven't set a number for np!) In particular, np->next asks to use the location np + someOffset (which is undefined!) to find your physical data (which is nowhere), and change it.
That's why you get a seg fault.
node_t list_array[10] should be node_t *list_array[10]
You have also, not malloced your node_t *np
node_t *np = malloc(sizeof(node_t));
For this program, I see no point to using dynamic storage duration (malloc). I would dispose of create_node in favour of memcpy, if you wish to keep all of your objects in static storage duration. For example,
#include <string.h>
typedef struct node
{
int value;
struct node *next;
struct node *prev;
} node_t;
int main(void) {
node_t list_array[10];
for (int i = 0; i < sizeof (list_array) / sizeof (*list_array); i++) {
memcpy(list_array + i,
&(node_t){ .value = rand() % 10,
.next = NULL,
.prev = NULL },
sizeof (*list_array));
}
return 0;
}

Resources