Reputation: 47
I am a beginner of learning c, and segmentation happens many times for me. I also did some research online about segmentation fault: some reasons are allocate memory problems, null pointer, or memory access problem. But I am confused that why sometimes the code work, but sometimes it says segmentation fault? Below is the code I am getting this issue in both the insertAt
and destroyList
functions:
#include <stdio.h>
#include <stdlib.h>
typedef struct NODE{
int data;
struct NODE* next;
} node;
node* insertAt(node*, int, int);
void printList(node*);
void destroyList(node*);
node* myList;
int counter = -1;
int main()
{
myList = NULL;
int pos, input;
myList = insertAt(myList, 0, 333);
myList = insertAt(myList, 0, 555);
myList = insertAt(myList, 1, 222);
myList = insertAt(myList, 3, 444);
printf("My List:\n");
printList(myList);
destroyList(myList);
printf("After Destroy:\n");
printList(myList);
return 0;
}
node* insertAt(node* head, int pos, int newData)
{
node* temp = (node*) malloc(sizeof(node));
temp->data = newData;
counter++;
if(head == NULL){
head = temp;
return head;
}else if (pos == 0)
{
temp->next = head;
head = temp;
return head;
}else if(head != NULL && pos > counter){
node* current = head;
node* temp2 = current;
while(current != NULL){
temp2 = current;
current = current->next;
}
temp->next = current;
temp2->next = temp;
return head;
}else
{
node* current = head;
while(pos-1>0){
current = current->next;
pos--;
}
temp->next = current->next;
current->next = temp;
return head;
}
}
void printList(node* head)
{
node* ptr = head;
while (ptr != NULL) {
printf("%i ", ptr->data);
ptr = ptr->next;
}
printf("\n");
}
void destroyList()
{
node* temp;
while(myList){
temp = myList;
myList = temp->next;
free(temp);
}
}
Upvotes: 0
Views: 9897
Reputation: 181159
I also did some research online about segmentation fault: some reasons are allocate memory problems, null pointer, or memory access problem.
A segmentation fault pretty much always indicates that your program attempted to access memory that does not belong to it, or in a way it is not permitted to access it. You can break it down into different ways that a program might do that, but the general rule is to ensure that you dereference only valid pointers, and that you attempt to modify only modifiable data.
But I am confused that why sometimes the code work, but sometimes it says segmentation fault?
C does not specify that any particular behavior produces a segmentation fault. The standard does not even contain the term "segmentation fault". It does, however, talk about "undefined behavior" -- which is what you get if you execute code that does not abide by the semantic rules of C and its standard library.
A segmentation fault is one possible manifestation of undefined behavior on many systems, but that is outside C's scope. C does not promise any particular form of undefined behavior under any particular circumstances -- it can't, for that behavior would then be defined, not undefined. It follows that one of the other forms of undefined behavior that could be seen is whatever behavior that the programmer intended. That is in fact seen sometimes.
Furthermore, it may be the case that a given program has undefined behavior -- which may manifest in a segmentation fault -- only under certain conditions, such as for particular inputs.
In any event, that your program sometimes or even always (as far as you can determine) behaves as intended does not prove that it is free of undefined behavior.
As for you particular code, there are several flaws in it that could lead it sometimes to exhibit undefined behavior. Among them are:
You use the return value of malloc()
without checking whether it is NULL. malloc()
signals memory allocation failure by returning NULL, and if you later attempt to dereference that then you invoke undefined behavior.
When it inserts the initial node into the list and when it adds a node at the end of the list, insertAt()
fails to set the new node's next
pointer, leaving that with an indeterminate value. When any function later traverses the list, it evaluates that indeterminate value, which produces undefined behavior. In practice, this might happen to have the expected behavior if the indeterminate value turned out to be a null pointer value. That is in no way guaranteed, but it is also not altogether unlikely.
Your main()
function attempts to print the list after it has been deallocated in destroyList()
, by passing the then-invalid myList
pointer to printList()
.
Upvotes: 2
Reputation: 165386
The tool you want is Valgrind. It will find all sorts of hidden memory problems for you.
For example, this code "works".
$ cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *string = calloc(3, sizeof(char));
strcpy(string, "foo");
printf("%s\n", string);
free(string);
return 0;
}
But Valgrind finds the subtle off-by-one memory error.
$ make
cc -Wall -g test.c -o test
$ ./test
foo
$ valgrind ./test
==62034== Memcheck, a memory error detector
==62034== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==62034== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==62034== Command: ./test
==62034==
==62034== Invalid write of size 1
==62034== at 0x10043B5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
==62034== by 0x1001B8421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x10022BBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x100000F2C: main (test.c:9)
==62034== Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd
==62034== at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
==62034== by 0x100000F11: main (test.c:7)
==62034==
==62034== Invalid read of size 1
==62034== at 0x10000B2C8: strlen (vg_replace_strmem.c:470)
==62034== by 0x1001EDA4B: __vfprintf (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x1002166C0: __v2printf (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x1001EC381: vfprintf_l (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x1001EA21B: printf (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x100000F42: main (test.c:10)
==62034== Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd
==62034== at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
==62034== by 0x100000F11: main (test.c:7)
==62034==
foo
==62034==
==62034== HEAP SUMMARY:
==62034== in use at exit: 26,553 bytes in 188 blocks
==62034== total heap usage: 273 allocs, 85 frees, 32,788 bytes allocated
==62034==
==62034== LEAK SUMMARY:
==62034== definitely lost: 0 bytes in 0 blocks
==62034== indirectly lost: 0 bytes in 0 blocks
==62034== possibly lost: 0 bytes in 0 blocks
==62034== still reachable: 0 bytes in 0 blocks
==62034== suppressed: 26,553 bytes in 188 blocks
==62034==
==62034== For counts of detected and suppressed errors, rerun with: -v
==62034== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 19 from 19)
It's a lot to ingest, but the important lines are in test.c and just above it. Looking at the first message...
==62034== Invalid write of size 1
==62034== at 0x10043B5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
==62034== by 0x1001B8421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x10022BBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==62034== by 0x100000F2C: main (test.c:9)
==62034== Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd
==62034== at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
==62034== by 0x100000F11: main (test.c:7)
This is an error message, followed by the stack of function calls that lead to it, and then any associated errors that might have caused it.
"Invalid write of size 1" tells me I've walked one byte off allocated memory. by 0x100000F2C: main (test.c:9)
says it happened on line 9 of test.c which is strcpy
, and the lines above in the call chain confirm that (it says stpcpy
because strcpy
is probably just a macro around stpcpy
).
"Address 0x100a8f6d3 is 0 bytes after a block of size 3 alloc'd" is telling me the error is probably the result of an erroneous memory allocation. by 0x100000F11: main (test.c:7)
says it's at test.c line 7 which is the calloc
call, confirmed by the next call in the stack, at 0x10000A1B9: calloc (vg_replace_malloc.c:715)
.
This all says I allocated one less byte than I needed to. You need 4 bytes for a 3 byte string since C strings have a trailing null character.
(P.S. Don't use strcpy
.)
But I am confused that why sometimes the code work, but sometimes it says segmentation fault?
This is because C allows you to scribble over whatever memory you want. Modern operating systems at least keep you within your own process's memory. This can cause all sorts of problems if you overwrite something important, or if you try to read or write to memory you're not supposed to... or you can get lucky! Since memory is allocated slightly differently each time, each run of a program with memory problems might act differently.
My example code above works even though it writes to invalid memory. It's just one byte, so maybe it got lucky and wrote somewhere nobody cared about. Or maybe calloc
allocated a little more memory than it needed to.
Point is, learn valgrind
, or other memory checker, run it, and fix anything it says is busted.
(P.S. Install your OS package for valgrind
. It will be tweaked for your operating system's bugs and quirks. Otherwise you might get all sorts of warnings about the operating system's own code.)
Upvotes: 1
Reputation: 693
Run your program under gdb, like this:
$ gdb ./a.out
(gdb) run
...
Segmentation fault
(gdb) bt
That will print a backtrace, showing the spot in your code where it caused the fault and any functions that called it. If the segfault happens in a library function, keep looking down the backtrace till you get to your code, and see what you can fix there.
Upvotes: 2