在做K&R练习时,我遇到了一种我无法理解的行为,并且在SOF上找不到任何解决方案:
该程序(将基数 10 整数转换为基数 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 segfaultn");
char s[MAXLINE];
itob(10, s, 10);
printf("%sn", s);
assert(strcmp(s, "10") == 0);
itob(11, s, 2);
printf("%sn", s);
assert(strcmp(s, "1011") == 0);
itob(100, s, 8);
printf("%sn", s);
assert(strcmp(s, "144") == 0);
itob(1337, s, 32);
printf("%sn", s);
assert(strcmp(s, "19p") == 0);
itob(127, s, 64);
printf("%sn", 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] = ' ';
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("%sn", s);
(,则itob的结果不再传递断言语句。
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] = ' ';
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 "264332377377377177",
b=10) at untitled2.c:103
103 s[i++] = numtochar(n % b);
(gdb)
它位于此代码块中:
do
{
s[i++] = numtochar(n % b);
} while ((n /= b) != 0);
主要问题是使用局部变量i
而不对其进行初始化(因此它包含变量位置的堆栈上的垃圾(。
换句话说,该程序包含一些未定义的行为,这就是导致SEG错误的原因。