Search code examples
cgccsegmentation-faultprintfkernighan-and-ritchie

Program segfaulting based only on printf statements


In doing a K&R exercise, I'm encountering a behavior that I can't make any sense of and for which I can't find any solutions here on SOF:

The program (converting base 10 ints to base n strings) runs fine with my extra printf() statements. As soon as I begin removing or commenting them out, the unexpected behavior begins

The code in full:

#include <stdio.h>
#include <string.h>
#include <assert.h>

#define MAXLINE 1000

char numtochar(unsigned n);
void reverse(char s[]);
void itob(int n, char s[], unsigned b);

int main() {
    // test numtochar
    assert(numtochar(1) == '1');
    assert(numtochar(11) == 'b');
    assert(numtochar(61) == 'Z');
    assert(numtochar(62) == '+');
    // test reverse
    char t[] = "TestiNg";
    reverse(t);
    assert(!strcmp(t, "gNitseT"));
    // test itob
    printf("if this is commented out, it will segfault\n");
    char s[MAXLINE];
    itob(10, s, 10);
    printf("%s\n", s);
    assert(strcmp(s, "10") == 0);
    itob(11, s, 2);
    printf("%s\n", s);
    assert(strcmp(s, "1011") == 0);
    itob(100, s, 8);
    printf("%s\n", s);
    assert(strcmp(s, "144") == 0);
    itob(1337, s, 32);
    printf("%s\n", s);
    assert(strcmp(s, "19p") == 0);
    itob(127, s, 64);
    printf("%s\n", s);
    assert(strcmp(s, "1/") == 0);
    return 0;
}

/* This numbering is not standard base-64, but will be consistent so long
 * as base <= 64
 * 0..63 => 0..9a..zA..Z+/
 */
char numtochar(unsigned n) {
    assert(n < 64);
    if (n < 10)
        return ('0' + n);
    else if (n >= 10 && n < 36)
        return ('a' + (n - 10));
    else if (n >= 36 && n < 62)
        return ('A' + (n - 36));
    else if (n == 62)
        return '+';
    else if (n == 63)
        return '/';
}

void reverse(char s[]) {
    int c, i, j;
    for (i=0, j=strlen(s)-1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
    return;
}

void itob(int n, char s[], unsigned b) {
    assert(b <= 64);
    int c, i, sign;
    if ((sign = n) < 0)
        n = -n;
    do {
        s[i++] = numtochar(n % b);
    } while ((n /= b) != 0);
    if (sign < 0)
        s[i++] = '-';
    s[i] = '\0';
    reverse(s);
    return;
}

As demonstrated below, if I run with all the printf statements, it runs as expected. If I comment out the first statement, it will run fine once but not again.

user@laptop:~/git/ansi_c/ch3 $ make ex05.o # no printf statements commented
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
if this is commented out, it will segfault
10
1011
144
19p
1/
user@laptop:~/git/ansi_c/ch3 $ make ex05.o # comment out first printf statement
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
10
1011
144
19p
1/
user@laptop:~/git/ansi_c/ch3 $ make ex05.o # resave after no changes
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
Segmentation fault (core dumped)

If however I uncomment out the first printf statement and comment out the second one (printf("%s\n", s);), the result of itob is no longer passing the assert statement.

user@laptop:~/git/ansi_c/ch3 $ make ex05.o # uncomment first printf, comment second
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
if this is commented out, it will segfault
101101
ex05.o: ex05.c:33: main: Assertion `strcmp(s, "1011") == 0' failed.
Aborted (core dumped)

gcc version is 7.2.1

If I remove all printf statements it also segfaults. Being new to C, I am unsure where I could be underallocating memory if that is indeed the problem, as all similar questions I saw revolved around use of malloc.


Solution

  • An important thing to learn in C, is that if you don't initialise a variable with a value and then access it, it will lead to undefined behaviour. Take this function for example...

    void itob(int n, char s[], unsigned b) {
        assert(b <= 64);
        int c, i, sign;
        if ((sign = n) < 0)
            n = -n;
        do {
            s[i++] = numtochar(n % b);
        } while ((n /= b) != 0);
        if (sign < 0)
            s[i++] = '-';
        s[i] = '\0';
        reverse(s);
        return;
    }
    

    What value does i have at the start? It might be 0, but what it'll actually be is whatever random value happened to be in memory at the time. Which is why un-commenting code changes things as it's affecting what the value of i has.

    Just changing it to start with the value 0 fixes your problem. Also removed c as it's not used

    int i = 0, sign;
    

    A good tool to use is valgrind - it will tell you where memory corruption and leaks occur.