Learning_C_Tools
Accessing invalid memory
Running the code below results in exit code 139, a SIGSEGV, a segmentation fault:
int main(int argc, char *argv[]) {
int *x;
x = NULL;
printf("*x: %d", *x);
}
We can investigate this with gcc -g -o null null.c && gdb null
:
(gdb) run
Starting program: /home/sam/code/ostep-homework/memory-api/null
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555154 in main (argc=1, argv=0x7fffffffdc58) at null.c:8
8 printf("*x: %d", *x);
How handy! gdb
shows the exact line and snippet where the memory violation occurs.
Similarly we can use valgrind
:
==78470== Memcheck, a memory error detector
==78470== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==78470== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==78470== Command: ./null
==78470==
==78470== Invalid read of size 4
==78470== at 0x109154: main (null.c:8)
==78470== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==78470==
==78470==
==78470== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==78470== Access not within mapped region at address 0x0
==78470== at 0x109154: main (null.c:8)
==78470== If you believe this happened as a result of a stack
==78470== overflow in your program's main thread (unlikely but
==78470== possible), you can try to increase the size of the
==78470== main thread stack using the --main-stacksize= flag.
==78470== The main thread stack size used in this run was 8388608.
==78470==
==78470== HEAP SUMMARY:
==78470== in use at exit: 0 bytes in 0 blocks
==78470== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==78470==
==78470== All heap blocks were freed -- no leaks are possible
==78470==
==78470== For lists of detected and suppressed errors, rerun with: -s
==78470== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Leaking memory
If instead we leak:
int main(int argc, char *argv[]) {
int *x = (int*) malloc(sizeof(int));
*x = 8;
printf("*x: %d and i'm not freeing it!", *x);
exit(0);
}
gdb
doesn't do anything to help:
(gdb) run
Starting program: /home/sam/code/ostep-homework/memory-api/unfreed
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
*x: 8 and i'm not freeing it![Inferior 1 (process 79352) exited normally]
but valgrind
shows us what's still in use at exit:
❯ valgrind --leak-check=full --show-leak-kinds=all ./unfreed
==79571== Memcheck, a memory error detector
==79571== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==79571== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==79571== Command: ./unfreed
==79571==
*x: 8 and i'm not freeing it!==79571==
==79571== HEAP SUMMARY:
==79571== in use at exit: 4 bytes in 1 blocks
==79571== total heap usage: 2 allocs, 1 frees, 1,028 bytes allocated
==79571==
==79571== 4 bytes in 1 blocks are still reachable in loss record 1 of 1
==79571== at 0x4841888: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==79571== by 0x109171: main (unfreed.c:6)
==79571==
==79571== LEAK SUMMARY:
==79571== definitely lost: 0 bytes in 0 blocks
==79571== indirectly lost: 0 bytes in 0 blocks
==79571== possibly lost: 0 bytes in 0 blocks
==79571== still reachable: 4 bytes in 1 blocks
==79571== suppressed: 0 bytes in 0 blocks
==79571==
==79571== For lists of detected and suppressed errors, rerun with: -s
==79571== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Not allocating enough memory
For this code
int main(int argc, char *argv[]) {
int *data = (int *) malloc(100); // should be 100 * sizeof(int)
data[100] = 0;
exit(0);
}
Again gdb
doesn't help us but valgrind
does:
==80746== Invalid write of size 4
==80746== at 0x109170: main (in /home/sam/code/ostep-homework/memory-api/badarr)
==80746== Address 0x4a561d0 is 224 bytes inside an unallocated block of size 4,194,032 in arena "client"
Accessing freed memory
When we access memory that we've previously freed:
int main(int argc, char *argv[]) {
int *data = (int *) malloc(100 * sizeof(int));
free(data);
printf("data[64]: %d\n", data[64]);
exit(0);
}
Surprisingly the program runs okay and just prints 0
, but it is not correct, and valgrind
again is the hero:
==81308== Command: ./badaccess
==81308==
==81308== Invalid read of size 4
==81308== at 0x10919C: main (in /home/sam/code/ostep-homework/memory-api/badaccess)
==81308== Address 0x4a56140 is 256 bytes inside a block of size 400 free'd
==81308== at 0x484426F: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==81308== by 0x109191: main (in /home/sam/code/ostep-homework/memory-api/badaccess)
==81308== Block was alloc'd at
==81308== at 0x4841888: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==81308== by 0x109181: main (in /home/sam/code/ostep-homework/memory-api/badaccess)
Invalid free
Luckily the compiler can warn us of invalid free
s:
int main(int argc, char *argv[]) {
int *data = (int *) malloc(100 * sizeof(int));
free(&data[64]);
exit(0);
}
results in
/home/sam/code/ostep-homework/memory-api/badfree.c:7:3: warning: ‘free’ called on pointer ‘data’ with nonzero offset 256 [-Wfree-nonheap-object]
7 | free(&data[64]);
| ^~~~~~~~~~~~~~~
/home/sam/code/ostep-homework/memory-api/badfree.c:6:23: note: returned from ‘malloc’
6 | int *data = (int *) malloc(100 * sizeof(int));
| ^~~~~~~~~~~~~~~~~~~~~~~~~
free(): invalid pointer
Because, as mentioned above, the free
function takes the exact pointer returned from malloc
.