This question already has answers here:
Why is the gets function so dangerous that it should not be used?
(13 answers)
Crash or "segmentation fault" when data is copied/scanned/read to an uninitialized pointer
(5 answers)
Closed 4 years ago.
When I try to run this code, all I get is a warning that gets() is too dangerous too use. And then when I run it, I get blank. I am suppose to display this:
Ollie 2.9 freshmen
John 3.2 senior
Julie 2.2 freshmen
Joe 1.8 freshmen
Mary 3.8 senior
Sue 3.4 junior
Jane 2.7 senior
Bob 2.8 senior
Fred 3.2 freshmen
Bill 3.3 junior
Here is my code:
Student *top = NULL; // to point top node of list
Student * temp, *temp1, *temp2; // for traversing list
// Creates the entire linked list from the file.
// Should call readNext and push
// Returns head of the linked list
Student *buildStudentList()
{
Student *p; // will hold the data to be pushed in list
p = readNext();
push(&top, p);
return top; //TODO: Change return
}
//Read a single line from standard input and return a student structure located on the heap
Student *readNext()
{
Student *s = (Student*)malloc(sizeof(Student)); // allocating dynamic memory in heap
printf("Please Enter Student Name, gpa, year :");
gets(s->name);
scanf("%f", &s->gpa);
gets(s->year);
s->next = NULL; // initially make next as NULL
return s; //TODO: Change return
}
//Return a student structure stored on the heap
Student *makeStudent(char *name, float gpa, char *year)
{
Student *s = (Student*)malloc(sizeof(Student));// allocating memory in heap
s->name = name;
s->gpa = gpa;
s->year = year;
s->next = NULL;
return s; //TODO: Change return
}
//insert a new student node at the head of the linked list
void push(Student **list, Student *student)
{
top = *list;
student->next = top; // assign current top node of list to be second node of the list
top = student; // make current node as top node of the list
printf("push successful.\n");
}
//Insert a student node in the desired position on the linked list
void insert(Student *list, Student *s, int position)
{
int i;
top = list;
temp = top;// temp is for traversing the list
for (i = 1; i < position - 1; i++) // loop to reach desired position in the list
{
temp = temp->next;
}
if (temp == NULL)
{
printf("Position does not exist.\n");
}
else
{
s->next = temp->next;
temp->next = s;
}
}
//Displays contents of a single student structure
void display(Student *s) {
printf("NAME:%s\t| GPA:%f\t| YEAR:%s\n", s->name, s->gpa, s->year);
}
//Displays contents of the entire linked list
void displayAll(Student *list)
{
temp = list;
while (temp != NULL)
{
display(temp);
temp = temp->next;
}
}
//Delete all data allocated on the heap before terminating program
void cleanUp(Student *list)
{
temp1 = list; // will point to the top node of list
temp2 = temp1->next; // will point to the second node of the list
while (temp1 != NULL)
{
free(temp1);
temp1 = temp2;
temp2 = temp2->next;
}
printf("Cleanup Successful.\n");
}
//Main function tests your functions.
int main()
{
Student *list, *s;
printf("Program Started ");
//Construct Linked List from Standard Input
list = buildStudentList();
//Insert a new student in desired position
s = makeStudent("Max", 3.0, "senior");
insert(list, s, 1);
//Display entire linked list
displayAll(list);
//Free all heap memory
cleanUp(list);
printf("Program Successful Exit ");
return 0;
//exit(EXIT_SUCCESS);
}
And here is the output I am getting:
Program Started
Segmentation fault
Should I try to use the fgets() instead of gets()? The output I am trying to do is part of a different file, does that have a affect to it?
Ignoring warnings is never the right thing to do. The aim is not just to remove the warnings using some hack but to address the root cause that produces warning. In this particular case, the compiler explicitly has warned you that using gets is too dangerous. Why would you not heed such a clear warning?
gets has been removed from the standard from C11 onwards.
The gets() function does not perform bounds checking, therefore this function is extremely vulnerable to buffer-overflow attacks. It cannot be used safely (unless the program runs in an environment which restricts what can appear on stdin). For this reason, the function has been deprecated in the third corrigendum to the C99 standard and removed altogether in the C11 standard. fgets() and gets_s() are the recommended replacements.
Never use gets().
Also look at your memory allocation/deallocation for the members of the struct.
Also read this question on whether the result of malloc should be cast.
Hello I went through your code, assuming that your structure description is as follows:
typedef struct Student_s{
char name[32];
float gpa;
char year[5];
struct Student_s* next;
}Student;
The problem with gets() is already pointed out in previous answer by P.W
. You could use scanf("%s",s->name) instead of gets(),
And in makeStudent() you should be using strcpy() for copying string. [ Reason ]
Also in cleanUp() consider the case when the last node is being freed, temp1 will be pointing to the last node and temp2 to NULL. In this situation if you do a temp2 = temp2->next it will result in segmentation fault. You could avoid that by enclosing the statement in an if.
if(temp2 != NULL){
temp2 = temp2->next;
}
Related
This question already has answers here:
Delete node from Linked List
(3 answers)
Closed 1 year ago.
I'm trying to delete a data from linked list loaded from drivers.txt and I haven't got any ideas to how to do it...
So to imagine this is my drivers.txt file.
***
Carl
RedBull
***
John
Mercedes
***
Jessica
Mercedes
***
Brandon
RedBull
I'm trying to remove every driver driving for RedBull. So when I print next time I want to see only John and Jessica.
My struct:
typedef struct drivers {
char *name;
char *team;
struct drivers *next;
} drivers_t;
How I load the drivers.txt.
drivers_t *drivers_load(const char *file){
int i = 0;
FILE *fp = fopen("drivers.txt", "r");
drivers_t *ptr = NULL;
drivers_t *head = NULL;
char buffer[255];
while (fgets(buffer, 255, fp) != NULL){
switch(i++){
case 0:
if( !ptr ) {
ptr = drivers_new();
head = ptr;
}
else {
ptr->next = drivers_new();
ptr = ptr->next;
}
break;
case 1:
ptr->name = strdup(buffer);
break;
case 2:
ptr->team = strdup(buffer);
break;
i = 0;
break;
}
}
return head;
}
My print function:
void drivers_print(drivers *head){
int number = 1;
for (drivers_t *drivers = head; drivers != NULL; drivers = drivers->next){
printf("%d.\n", number++);
printf("Driver: %s", drivers->presenter);
printf("Team: %s", drivers->birth_number);
}
}
Thanks in advance for any ideas.
Deleting node:
void node_delete(drivers_t *head){
char team[32];
scanf("%s", &team);
strcat(team, "\n");
for (node_t *node = head; node != NULL; node = node->next){
if(strcmp(node->team, team) == 0){
drivers_t *temp;
temp = head->next;
head->next = temp->next;
free(temp);
}
else{}
}
}
If you have a list like this:
A -> B -> C -> D -> E
and you want to delete C, all you need to do is to ensure that the next pointer of B now points to D, so you get:
A -> B -> D -> E
Basically the algorithm is:
Go down the list and find C, the node you want to remove.
While going down the list, always also remember the last node you have seen, which would be B in that case.
Once you reached C, assign the next pointer value of C to the next pointer value of B and you are done, as now going over the list directly jumps from B to D.
Finally clean up C, which means free any memory C or any of its struct fields are using.
The only tricky case is the first node, as removing A is a special case, in which case you must set the list pointer itself to the next value of A, so the list now starts with B. You can recognize that case easily, though, as in step (2) you have remembered the last node. When you start, there is no last node, so initially that value should start with NULL. If you found the node (step 3) and the last one is NULL, you will know that you just hit the first node and you can handle that case appropriately.
PREFACE: The goal is to prompt a user for input, adding each element (input line) into a linked list.
I have been playing around with some sample code from Learn-C.org, which shows a linked list example.
I have modified the code so that it takes "strings" instead of integers.
My insert function is as follows:
void push(node_t * head, char *data) {
node_t * current = head;
if(head == NULL) {
printf("First element ever!\n");
}
else if(current->data == NULL) {
current->data = data;
current->next = NULL;
}
else {
while (current->next != NULL) {
current = current->next;
}
current->next = malloc(sizeof(node_t));
current->next->data = data;
current->next->next = NULL;
}
}
Now, in MAIN, I initiate the list as follows:
node_t * test_list = malloc(sizeof(node_t));
Adding elements is done with:
push(test_list, "FOO");
push(test_list, "FEE");
push(test_list, "FAA");
When printing the list, using print_list(test_list), I get the following output:
FOO
FEE
FAA
PROBLEM
However, I have then included a while loop that prompts user for input and adds this to the linked list.
char command[120];
int counter = 0;
while(counter < 3) {
printf("Enter element: ");
fgets((void *)command, sizeof(command), stdin);
push(test_list, command); //Insert
counter++;
}
However, this does not add each element into the link list. Instead, it adds the LAST element into the list three times.
For instance, when providing:
Enter element: Argentina
Enter element: Mexico
Enter element: Sweden
The list prints out as:
FOO
FEE
FAA
Sweden
Sweden
Sweden
EDIT (added print function)
My print function is as follows:
void print_list(node_t * head) {
node_t * current = head;
printf("**** Printing list ****\n");
while (current != NULL) {
printf("%s\n", current->data);
current = current->next;
}
}
What am I missing, and alternatively: How can I fix this? Any help is highly appreciated.
Use strdup to return a copy of the string allocated on the heap.
The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).
node_t *test_list = malloc(sizeof(node_t));
test_list->next = NULL;
test_list->data = NULL;
while(counter < 3) {
printf("Enter element: ");
fgets((void *)command, sizeof(command), stdin);
push(test_list, strdup(command)); //Insert
counter++;
}
You have an array command with capacity of 120. You use this to read in values. This is ok.
Then you send a pointer to this array to be stored. It gets stored, everything is still fine.
The next time you read input, you read it to the same array, for which you gave a pointer to be stored. So you are changing the content of the memory where that pointer points to. This is not ok.
You need to allocate separate memory areas for each string and handle their deallocations. strdup is the easiest way to get a new memory block with the contents of the user input.
But do remember that you really have to deallocate the memory when you don't need it anymore. In this case you might never remove any strings, but when you do, you can't just remove the element, you have to also free the memory used by the string.
current->data = data; here you are copy only pointer address not data, on that address (address of 'command') last data ('Sweden') will be available.
you should use strcpy(current->data,data) to copy data.
I have been tasked with writing a function that must edit a node in a linked list. I have the following code so far that seems to be somewhat working, but there are still some problems:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct flavor {
char *name;
int available;
struct flavor *next;
};
// "Head" node
struct flavor *first = NULL;
// Current node
struct flavor *current = NULL;
void insert(char *name, int available) {
struct flavor *node = (struct flavor*)malloc(sizeof(struct flavor));
node->name = name;
node->available = available;
node->next = NULL;
if (first == NULL) {
first = node;
return;
}
current = first;
while (current->next != NULL) {
current = current->next;
}
current->next = node;
}
void sort(struct flavor *first) {
char *i;
struct flavor *temp1;
struct flavor *temp2;
for (temp1 = first; temp1 != NULL; temp1 = temp1->next) {
for (temp2 = temp1->next; temp2 != NULL; temp2 = temp2->next) {
if (temp2->name < temp1->name) {
i = temp1->name;
temp1->name = temp2->name;
temp2->name = i;
}
}
}
}
void display() {
struct flavor *ptr = first;
printf("Available shake flavors\n");
while (ptr != NULL) {
printf("\nName: %s\n", ptr->name);
printf("Available: %d\n", ptr->available);
ptr = ptr->next;
}
}
struct flavor *edit_flavor(struct flavor *first, char *old_name, char *new_name) {
if (first == NULL) {
printf("List is empty");
return;
}
current = first;
while (current->next != NULL) {
if (current->name == old_name) {
current->name = new_name;
printf("\nFlavor %s replaced with %lu\n", old_name, new_name);
return;
}
current = current->next;
}
printf("\n%s does not exist in the list\n", old_name);
}
int main(void) {
char *old = "";
char *new = "";
insert("Banana", 1);
insert("Mango", 0);
insert("Strawberry", 1);
insert("Apple", 1);
sort(first);
display();
printf("\nPlease enter old flavor: ");
scanf("%s", &old);
printf("Please enter new name: ");
scanf("%s", &new);
edit_flavor(first, old, new);
sort(first);
display();
return 0;
}
Whenever I try to use the program at all, I keep getting the error segmentation fault (core dumped) and I am not sure what I have done wrong?
On top of that, my insert into the "Linked List" seems to be working just fine, but I need the list to be in alphabetical order and it also needs to be updated in alphabetical order once a node has been edited. I have attempted to implement the sort() function, but it is not working at all and I think it may be due to the fact that I'm not comparing the names correctly (as they are strings?). How can I fix the sort function in order to correctly alphabetize the list?
For starters, do not use global variables such as first and current.
The function interface is inconsistent. For example the function edit_flavo, on the one hand, accepts the head node first through its first parameter. On the other hand, it uses the global variable current.
The function sort is incorrect. It does not swap values of the data member available. This comparison in the if statement
if (temp2->name < temp1->name) {
does not make sense because there are compared addresses of string literals instead of comparing the pointed string literals themselves using the C string function strcmp.
The list should not keeps addresses of string literals but it should make copies of passed strings in a dynamically allocated memory.
And it will be more correct to swap entire nodes in the sort function instead of swapping data members of pointed nodes.
In function edit_flavor this if statement
if (current->name == old_name) {
in general also is incorrect. Whether equal string literals are stored by the compiler as separate character arrays or as a one array depends on the a compiler options. So it is not necessary that for example the condition in such an if statement
if ( "Hello" == "Hello" )
will yield true. Again you need to use the C standard string function strcmp to compare pointed string literals.
In Main you declared two pointers to string literals.
char *old = "";
char *new = "";
You may not change string literals. Any attempt to change a string literal results in undefined behavior. You need to declare character arrays where the strings entered by the user will be stored. So one more the list shall store deep copes of strings not their addresses.
These statements
scanf("%s", &old);
scanf("%s", &new);
are incorrect. Firstly the argument expressions shall have the type char * as the type of variables old and new instead of the type char ** that the expressions &old and &new have. And secondly such a call invokes undefined behavior because the function scanf will try to change the string literals pointed to by the pointers.
So you need to redesign your list. Start from removing the global variable first and the pointer to the head node make a local variable in main initializing it by NULL. Then change the function insert by allocating dynamically memory for a copy of passed to the function string. Rewrite the function sort that will swap entire nodes instead of separate data members.
And for entered strings use character arrays.
#include<stdio.h>
#include<stdlib.h>
struct nodetag{
int ndata;
struct nodetag *pNext;
};
typedef struct nodetag nodetag;
int main(){
nodetag *p1=NULL,*bago,*last,*ptemp;
int i,x,y;
printf ("\nEnter number of nodes: ");
scanf ("%d", &i);
y=i;
while (i)
{
if (p1 == NULL)
{
p1 = malloc(sizeof(nodetag));
last = p1;
p1->pNext = NULL;
scanf("%d",&p1->ndata);
}
else
{
bago = malloc(sizeof(nodetag));
last->pNext = bago;
bago->pNext = NULL;
scanf("%d",&bago->ndata);
}
i--;
}
ptemp = p1;
for(x=0;x<y;x++){
printf("%d",ptemp->ndata);
ptemp=ptemp->pNext;
}
getch();
return 0;
}
can i copy the whole nodes to ptemp to avoid the pfirst getting moved?
when i ran the code it seems that it only prints the first and last data of the nodes.
how can i access the second node when the number of nodes is more than 2?
im focusing on printing the data in the nodes
ptemp = p1;
for(x=0;x<y;x++){
printf("%d",ptemp->ndata);
ptemp=ptemp->pNext;
this seems that it crashes the whole program
bago->pNext = NULL;
After this add this line:
last = bago;
Now errors in your code:
You are not updating the last pointer hence every time you assign a value it is overwritten on the second value assigned.
You have never freed the mallocd memory, before exiting main() free all the memory by iterating over the list as you do while printing.
getch() is not a standard library function, hence not portable, you should use getchar() instead.
EDIT: (On OP's comment)
Well, try to understand it yourself, its very simple. Step over/Execute the code manually by hand.
else
{
bago = malloc(sizeof(nodetag));
last->pNext = bago;
bago->pNext = NULL;
scanf("%d",&bago->ndata);
last = last->pNext; /*you need add this statements*/
}
sorry ,my English is not good.
I can not explain it in English.
I have created a single linked list. Everything works fine.
I just want to know if I have done anything potentially dangerous in my code. The code snippets I am concerned about is my push, pop, and clean-up. The parts of the code is just for user interaction so not really important (I posted anyway so that it was more clear in what I was doing). Just the linked list application.
Many thanks for any suggestions, as this is my fist attempt.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct product_data product_data_t;
struct product_data
{
int product_code;
char product_name[128];
int product_cost;
product_data_t *next;
};
static product_data_t *head = NULL;
static product_data_t *tail = NULL;
static product_data_t *new_product = NULL;
// Push a product on to the list.
void push(int code, char name[], int cost);
// Pop (delete) a product from the list.
void pop(int code);
// Display all product in the list.
void display_list();
// Delete all memory allocated on the list
void clean_up();
// Display menu
void menu();
int main(void)
{
menu();
getchar();
return 0;
}
void push(int code, char name[], int cost)
{
// Allocate memory for the new product
new_product = calloc(1, sizeof(product_data_t));
if(!new_product)
{
fprintf(stderr, "Cannot allocated memory");
exit(1);
}
/* Populate new products elements fields */
new_product->product_code = code;
strncpy(new_product->product_name, name, sizeof(new_product->product_name));
new_product->product_cost = cost;
new_product->next = NULL;
// Set the head and tail of the linked list
if(head == NULL)
{
// First and only product
head = new_product;
}
else
{
tail->next = new_product;
}
tail = new_product;
}
// Find the product by code and delete
void pop(int code)
{
product_data_t *product = head;
product_data_t *temp = NULL;
product_data_t *previous = head;
int found = 0; // 0 - Not Found, 1 - Found
if(!head)
{
puts("The list is empty");
return;
}
while(product)
{
if(product->product_code == code)
{
found = 1; // Found
// Check if this is in the first node - deleting from head
if(head->product_code == code)
{
temp = head;
head = head->next;
free(temp);
// Finished Deleting product
return;
}
// Check if this is the end node - deleting from the tail
if(tail->product_code == code)
{
temp = tail;
tail = previous;
free(temp);
// Finished deleting product
return;
}
// delete from list if not a head or tail
temp = product;
previous->next = product->next;
free(temp);
// Finished deleting product
return;
}
// Get the address of the previous pointer.
previous = product;
product = product->next;
}
if(!found)
{
printf("code [ %d ] was not found\n", code);
}
// Set all to null after finished with them
product = NULL;
temp = NULL;
previous = NULL;
}
// Traverse the linked list
void display_list()
{
// Start at the beginning
product_data_t *product = head;
while(product)
{
printf("===================================\n");
printf("Product code: \t\t%d\n", product->product_code);
printf("Product name: \t\t%s\n", product->product_name);
printf("product cost (USD): \t%d\n", product->product_cost);
printf("===================================\n\n");
// Point to the next product
product = product->next;
}
// Finished set to null
product = NULL;
}
// Release all resources
void clean_up()
{
product_data_t *temp = NULL;
while(head)
{
temp = head;
head = head->next;
free(temp);
}
head = NULL;
temp = NULL;
// End program - goodbye
exit(0);
}
void menu()
{
int choice = 0, code = 0, cost = 0;
char name[128] = {0};
do
{
fflush(stdin); // Flush the input buffer
puts("========= Welecome to linked list ===============");
puts("[1] Add new product to the list");
puts("[2] Delete a product from the list");
puts("[3] Display all products");
puts("[4] Exit and clean up");
printf("Enter your choice: ");
scanf("%d", &choice);
switch(choice)
{
case 1:
printf("Enter product code: ");
scanf("%d", &code);
printf("Enter cost: ");
scanf("%d", &cost);
printf("Enter name: ");
scanf("%s", name);
push(code, name, cost);
break;
case 2:
printf("Enter product code: ");
scanf("%d", &code);
pop(code);
break;
case 3:
display_list();
break;
case 4:
clean_up();
break;
default:
puts("Incorrect choice");
break;
}
}while(choice != 4);
}
From pop()
if(head->product_code == code)
{
temp = head;
head = head->next;
free(temp);
// Finished Deleting product
return;
}
In the case of there only being one item, 'head' and 'tail' would be pointing to the same node. However, if you pop this one item, 'head' will be adjusted but 'tail' will still be pointing to the free'd node. This will leave a bad pointer, which may cause your computer to explode.
Addendum: Similarly, 'new_product' will be dangling if you ever pop the last node that was pushed, and clean_up() will leave the 'tail' pointer dangling as well. Even if the code sample provided will never dereference these after they're free'd, dangling pointers in C code should always be treated as "potentially dangerous".
strncpy(new_product->product_name, name, sizeof(new_product->product_name));
if the string is longer than the size you have it won't be terminated correctly.
I see no reason why new_product should be global and every reason why it should not be.
It looks like you're on the right track, but there are issues. I would remove the global variables, and instead have a list_t struct (containing head and tail) that you pass into functions. As others have noted, you may also want to make the list generic by using (e.g.) a node_t type and void* data pointer.
Generally push and pop are used to refer to adding or removing an item at the beginning, not an arbitrary location (as you do); this is just a question of naming.
If you had product_name char *product_name instead, that would allow you to remove the length limitation as well as the need for strncpy. You would just have the caller allocate the string, and then free it in clean_up.
You could consider using a enum to improve your menu's readability. For "Check if this is in the first node - deleting from head" (same for tail), you should just compare head to product, not compare the codes.
After "tail = previous", you should set tail->next to NULL.
Agree with the issues raised by goldPseudo and thaggie/Steven.
In push(), replace strncpy() with strlcpy() to ensure the destination string is always NUL terminated.
In cleanup(), I'd suggest that you remove the exit(0); statement -- you don't need it. Exiting a programme from within a subroutine is generally not the best thing to do.
You should take away one lesson from creating your first singly linked list, and that is, singly linked lists are generally not very useful in the real world because:
They're too hard to manipulate. Just look at the complexity of your pop() subroutine.
Relatively slow because you have to start at the beginning of the list each time you want to retrieve an element from the list.
You should now attempt to write your first doubly linked list. While doubly linked lists are more complex to implement, they are easier to manipulate (especially when deleting an element) than singly linked lists.
Is there any reason you call exit(0) from clean_up function? I think this is potential dangerous, since you don't give a chance to the user to finish program correctly.
As well I would suggest you to use data encapsulation when you building up you data structure:
typedef struct
{
int product_code;
char product_name[128];
int product_cost;
list_node *next;
} list_node;
typedef struct
{
list_node* head;
list_node* tail;
list_node* current;
int size;
} list;
Also it's a good practice to use trail dummy node at the head of your list to make your code more generic.
Following normal naming convensions, push and pop are related to stacks - i.e. push() should add an item to the top of the stack (you add to the tail of the list, which is fine!), and pop() should return and remove the item from the top of the stack (you search for a named item anywhere in the list and remove it.)
Function names aside, I would suggest a more generic (abstract) implementation of the list, where the content of a node is a pointer to arbitrary data (which in your special case will later be a product_data). This way your linked list can be re-used for any content, and is easier to debug, read and to maintain.
It would also be a better idea not to have stuff global, but rather permit multiple instances of a list. The normal C way is to keep the data in a struct, and then to pass an instance as first argument to each function.