dcc

Learning Outcome

Understand the benefits of dcc and interpret the output of dcc’s inbuilt debugging capabilities.

Introduction

It is recommended that COMP1511 students use dcc. dcc is a compiler that uses the tools Valgrind and ASan to provide extra help to beginner programmers about memory errors and runtime errors. It can even be used to check for memory leaks.

Applicable subjects

COMP1511


Using dcc

dcc compiles and runs in a very similar manner to gcc:

$ dcc -o <program> <program.c>
$ ./program

If your program doesn’t have any undefined behaviour or memory errors, it should produce the same output as if you had compiled with gcc. dcc only provides additional output when your program has a variety of issues such as accessing invalid array indicies, and dereferencing a NULL pointer.

Examples of how to interpret this output are provided in the examples below.

Checking for memory leaks with dcc

dcc can be used to check for memory leaks (i.e. malloced memory that hasn’t been freed). To use this feature, compile your program with - -leak-check:

$ dcc --leak-check <program> <program.c>

Examples

Dereferencing NULL

When a NULL pointer is dereferenced, there are vast differences between the errors produced by the executables compiled with gcc, compared with those compiled with dcc.

dereferencing a NULL pointer

broken_linked_list.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
    int data;
    struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list, int length);

int main(void){
    int length1 = 7;
    struct node *list1 = create_list(length1);
    print_list(list1, length1);

    return 0;
}

struct node *create_node(int data){
    struct node *new = malloc(sizeof(struct node));
    assert(new != NULL);
    new->data = data;
    new->next = NULL;
    return new;
}

struct node *create_list(int length) {

    struct node *head = NULL;
    if (length > 0) {
        head = create_node(0);
        int i = 1;
        struct node *curr = head;
        while (i < length) {
            curr->next = create_node(i);
            curr = curr->next;
            i++;
        }
    }
    return head;
}

void print_list(struct node *list, int length){
    struct node *curr = list;
    int i = 0;
    while (i <= length) {
        printf("%d->", curr->data);
        curr = curr->next;
        i++;
    }
    printf("X\n");
}

gcc

$ gcc -o broken_linked_list broken_linked_list.c
$ ./broken_linked_list
Segmentation fault

dcc

$ dcc -o broken_linked_list broken_linked_list.c
$ ./broken_linked_list

broken_linked_list.c:51:30: runtime error - accessing a field via a NULL pointer

dcc explanation: You are using a pointer which is NULL
A common error is  using p->field when p == NULL.

Execution stopped in print_list(list=0x602000000030, length=7) in broken_linked_list.c at line 51:

void print_list(struct node *list, int length){
    struct node *curr = list;
    int i = 0;
    while (i <= length) {
-->     printf("%d->", curr->data);
        curr = curr->next;
        i++;
    }

Values when execution stopped:

curr = NULL
i = 7
length = 7

Function Call Traceback
print_list(list=0x602000000030, length=7) called at line 18 of broken_linked_list.c
main()

Compiling and running with gcc only tells us that our program crashed because of a “segmentation fault”, which only tells us that we accessed an illegal part of memory. dcc, however, tells us exactly what went wrong:


runtime error - accessing a field via a NULL pointer

This means that the program encountered an error while running. The error occurred because we used the arrow/stab operator on a NULL pointer (e.g. NULL->data or NULL->next).


Execution stopped in print_list(list=0x602000000030, length=7) in broken_linked_list.c at line 51:
void print_list(struct node *list, int length){
    struct node *curr = list;
    int i = 0;
    while (i <= length) {
-->     printf("%d->", curr->data);
        curr = curr->next;
        i++;
    }

These lines mean that the error was encountered on line 51 of broken_linked_list.c. The lines around line 51 are printed out and the line of interest is indicated with an arrow. The data field of the curr pointer is being accessed on this line. We know from previous output, that we are dereferencing a NULL. We now suspect that curr is NULL at this line.


Values when execution stopped:
curr = NULL
i = 7
length = 7

These lines provide the values of our variables when we the error happened. We now know for sure that curr was indeed NULL on line 51. We also know that the segfault occurred when i = 7 (i.e. on iteration 7 of the while loop). However, we should never reach the 7th iteration of the loop, as there are only 7 nodes. We appear to have gone off the end of the list.

An off by one error is common and would cause the while loop to go for one more or one less loop than desired. Line 50 stops the loop when i is greater than length (i.e. when i = 8). We want to exit the loop when i = 7, so this is most likely causing our issues.

Looking at the code, we may realise that, not only is there an off by one error, but there is a better way to traverse a linked list to its end. This is achieved by ending the loop when a NULL is reached. This adds some protection against an incorrect length passed in.

We fix this code with the new function and no more segfault! ::

$ ./linked_list.c 0->1->2->3->4->5->6->X

linked_list.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
    int data;
    struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
    struct node *list1 = create_list(7);
    print_list(list1);

    return 0;
}

struct node *create_node(int data){
    struct node *new = malloc(sizeof(struct node));
    assert(new != NULL);
    new->data = data;
    new->next = NULL;
    return new;
}

struct node *create_list(int length) {

    struct node *head = NULL;
    if (length > 0) {
        head = create_node(0);
        int i = 1;
        struct node *curr = head;
        while (i < length) {
            curr->next = create_node(i);
            curr = curr->next;
            i++;
        }
    }
    return head;
}

void print_list(struct node *list){
    struct node *curr = list;

    while (curr != NULL) {
        printf("%d->", curr->data);
        curr = curr->next;
    }
    printf("X\n");
}

Illegal Array Index Access Example

Compiling with gcc lets us access illegal array indices, however, compiling with dcc often tells that there is an issue as the code is compiling.

illegal_array.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main(void){
    
    int array[10];
    int i = 0;
    while (i <= 10) {
        array[i] = i;
        i++;
    }

    i = 0;
    while (i <= 10) {
        printf("%d ", array[i]);
        i++;
    }

    printf("\n");

    return 0;
}

gcc

$ gcc -o illegal_array illegal_array.c
$ ./illegal_array
0 1 2 3 4 5 6 7 8 9 10

dcc

$ dcc -o illegal_array illegal_array.c
illegal_array.c: In function \u2018main\u2019:
illegal_array.c:15:9: warning: iteration 10 invokes undefined behavior [-Waggressive-loop-optimizations]
        printf("%d ", array[i]);
        ^~~~~~~~~~~~~~~~~~~~~~~
illegal_array.c:14:11: note: within this loop
    while (i <= 10) {
        ^
illegal_array.c:9:18: warning: iteration 10 invokes undefined behavior [-Waggressive-loop-optimizations]
        array[i] = i;
        ~~~~~~~~~^~~
illegal_array.c:8:11: note: within this loop
    while (i <= 10) {

In the code provided above, gcc lets us access invalid indicies without any warning (which might not cause a problem in this instance, but it is undefined behaviour and can cause some annoying bugs). dcc, however,picks up that we are reading past the end of an array during compile time. It tells us which lines we need to check as well as which iteration will cause undefined behaviour.


illegal_array.c:15:9: warning: iteration 10 invokes undefined behavior [-Waggressive-loop-optimizations]
        printf("%d ", array[i]);
        ^~~~~~~~~~~~~~~~~~~~~~~
illegal_array.c:14:11: note: within this loop
    while (i <= 10) {

This tells us that on the 10th iteration of the while loop on line 14, there is “undefined behaviour”. The undefined behaviour occurs on line 15 which is an array access.


illegal_array.c:9:18: warning: iteration 10 invokes undefined behavior [-Waggressive-loop-optimizations]
        array[i] = i;
        ~~~~~~~~~^~~
illegal_array.c:8:11: note: within this loop
    while (i <= 10) {

This tells us that we have the same issue on line 9 as we did on line 15. An array access which causes undefined behaviour.


We then consider what occurs on iteration 10 and realise that our array is only of length 10 - there shouldn’t be a 10th iteration. There is an off by one error caused by the <= operator in both loops.


$ dcc -o illegal_array illegal_array.c
$ ./illegal_array
0 1 2 3 4 5 6 7 8 9

We change the <= to a < and the program runs as expected.


Leak Checking Example

If we call malloc() in our program, and it exits without calling free(), then we have a memory leak. Compiling and running with gcc, will not inform us of this memory leak, however, compiling and running with dcc –leak-check will. The program below illustrates this feature.

linked_list.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
    int data;
    struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
    struct node *list1 = create_list(7);
    print_list(list1);

    return 0;
}

struct node *create_node(int data){
    struct node *new = malloc(sizeof(struct node));
    assert(new != NULL);
    new->data = data;
    new->next = NULL;
    return new;
}

struct node *create_list(int length) {

    struct node *head = NULL;
    if (length > 0) {
        head = create_node(0);
        int i = 1;
        struct node *curr = head;
        while (i < length) {
            curr->next = create_node(i);
            curr = curr->next;
            i++;
        }
    }
    return head;
}

void print_list(struct node *list){
    struct node *curr = list;

    while (curr != NULL) {
        printf("%d->", curr->data);
        curr = curr->next;
    }
    printf("X\n");
}

gcc

$ gcc -o linked_list linked_list.c
$ ./linked_list
0->1->2->3->4->5->6->X

dcc

$ dcc -o linked_list linked_list.c
$ ./linked_list
0->1->2->3->4->5->6->X

dcc with leak check

$ dcc --leak-check -o linked_list linked_list.c
$ ./linked_list 0->1->2->3->4->5->6->X
$ Error: free not called for memory allocated with malloc in function create_node in linked_list.c at line 24.

This output means that the memory allocated in the malloc call on line 24 in linked_list.c, was never freed. This line is:

struct node *new = malloc(sizeof(struct node));

While it may be obvious in this program which malloc call is allocating memory which hasn’t been freed (because there is only one line with a malloc call on it), these line numbers are helpful if you have many different mallocs on different lines of your program.

Module author: Liz Willer <e.willer@unsw.edu.au>

Date

2020-01-15