仅基于 printf 语句的程序段错误

Program segfaulting based only on printf statements

在进行 K&R 练习时,我遇到了一种我无法理解的行为,而且我在 SOF 上找不到任何解决方案:

程序(将 base 10 整数转换为 base n 字符串)运行我的额外 printf() 语句没问题。一旦我开始删除或注释掉它们,意想不到的行为就开始了

完整代码:

#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] = '[=10=]';
    reverse(s);
    return;
}

如下所示,如果我 运行 使用所有 printf 语句,它会 运行 符合预期。如果我注释掉第一条语句,它将 运行 一次正常,但不会再次正常。

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)

但是,如果我取消注释掉第一个 printf 语句并注释掉第二个语句 (printf("%s\n", s);),itob 的结果将不再通过 assert 语句。

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 版本为 7.2.1

如果我删除所有 printf 语句,它也会出现段错误。作为 C 的新手,我不确定如果这确实是问题所在,我可能在哪里分配内存不足,因为我看到的所有类似问题都围绕着 malloc 的使用展开。

在 C 中要学习的一件重要事情是,如果您不使用值初始化变量然后访问它,它将导致未定义的行为。以这个函数为例...

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] = '[=10=]';
    reverse(s);
    return;
}

i 一开始有什么价值?它可能是 0,但它实际上是当时内存中恰好存在的随机值。这就是为什么取消注释代码会改变事情,因为它会影响 i 的值。

只需将其更改为以值 0 开头即可解决您的问题。还删除了 c,因为它未被使用

int i = 0, sign;

一个很好用的工具是 valgrind - 它会告诉您内存损坏和泄漏发生的位置。

运行 gdb 发布的代码导致:

注意:``untitled2` 是我给你的程序起的名字。

gdb untitled2

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from untitled2...done.

(gdb) br main
Breakpoint 1 at 0x4008e1: file untitled2.c, line 12.

(gdb) r
Starting program: /home/rkwill/Documents/forum/untitled2 

Breakpoint 1, main () at untitled2.c:12
12  {

(gdb) c
Continuing.
if this is commented out, it will segfault

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400d2a in itob (n=10, s=0x7fffffffda80 "427777", 
    b=10) at untitled2.c:103
103         s[i++] = numtochar(n % b);
(gdb) 

在这个代码块中:

do
{
    s[i++] = numtochar(n % b);
} while ((n /= b) != 0);

主要问题是在未初始化的情况下使用局部变量 i(因此它包含变量位置堆栈中的垃圾。)

换句话说,程序包含一些未定义的行为,这就是导致段错误的原因。