[英]why does this lead to core dump?
#include <ctype.h>
#include <stdio.h>
int atoi(char *s);
int main()
{
printf("%d\n", atoi("123"));
}
int atoi(char *s)
{
int i;
while (isspace(*s))
s++;
int sign = (*s == '-') ? -1 : 1;
/* same mistake for passing pointer to isdigit, but will not cause CORE DUMP */
// isdigit(s), s++;// this will not lead to core dump
// return -1;
/* */
/* I know s is a pointer, but I don't quite understand why code above will not and code below will */
if (!isdigit(s))
s++;
return -1;
/* code here will cause CORE DUMP instead of an comile-time error */
for (i = 0; *s && isdigit(s); s++)
i = i * 10 + (*s - '0');
return i * sign;
}
当我不小心弄错了在“s”之前缺少 * 运算符时,我得到了“分段错误(核心已转储)”,然后我得到了这个令人困惑的错误。 为什么“(,isdigit(s))”导致核心转储而“isdigit(s); s++”。 将不会。
来自isdigit [强调]
如果 ch 的值不能表示为 unsigned char且不等于 EOF,则行为未定义。
来自isdigit [强调]
c 参数是一个 int,应用程序应确保其值是一个可表示为无符号字符或等于宏 EOF 值的字符。 如果参数有任何其他值,则行为是 undefined 。
https://godbolt.org/z/PEnc8cW6T
未定义的行为包括它可能执行不正确(崩溃或静默生成不正确的结果),或者它可能偶然地完全按照程序员的意图进行。
您正在调用未定义的行为。 isdigit()
应该接收一个int
参数,但是您传入了一个指针。 这实际上是在尝试将指针分配给int
(外部参考:语言/表达式/赋值运算符/简单赋值,¶1)。
此外,有一个约束,即isdigit()
的参数可以表示为unsigned char
或等于EOF
。 (外部参照:库/字符处理<ctype.h>
,¶1)。
作为猜测, isdigit()
function 可能正在执行某种表格查找,输入值可能导致 function 访问表格之外的指针值。
到目前为止的所有答案都未能指出实际问题,即在 C 中分配期间不允许隐式指针指向 integer 转换。此处有详细信息: “Pointer from integer/integer from pointer without a cast”问题
特别是 C17 6.5.2.2/7
如果表示被调用 function 的类型包含原型,则 arguments 会隐式转换为相应参数的类型,就像通过赋值一样
“如同通过分配”让我们检查上述链接中引用的分配规则 6.5.16.1。 所以isdigit(s)
相当于这样的东西:
char* s;
...
int param_to_isdigit = s; // constraint violation of 6.5.16.1
此处编译器必须发出诊断消息。 如果您没有发现它,或者如果您使用的是给出警告而不是错误的工具链,请查看为初学者学习推荐哪些编译器选项 C? 这样你就可以防止编译这样的代码,这样你就不必花时间排除编译器已经为你发现的错误。
此外,ctype.h 函数要求传递的 integer 必须可表示为unsigned char
,但这是另一回事了。 C17 7.4 字符处理<ctype.h>:
在所有情况下,参数都是 int,其值应表示为 unsigned char 或应等于宏 EOF 的值
为什么没有来自
isdigit(s), s++;
?
首先。 未定义的行为可以通过多种方式表现出来,包括程序按预期工作。 这就是未定义的意思。
但是该行不等同于您的 if 语句。 它的作用是执行isdigit(s)
,丢弃结果,递增s
并丢弃该操作的结果。
但是, isdigit
没有副作用,因此编译器很可能只是删除了对 function 的调用,并将这一行替换为无条件s++
。 这可以解释为什么它不会出现段错误。 但是您必须研究生成的程序集才能确定,但这是有可能的。
您可以在此处阅读有关逗号运算符的信息 逗号运算符做什么?
我无法在 MacOS/Darwin 中重复此行为,但我可以在 Debian Linux 中重复。
为了进一步调查,我编写了以下程序:
#include <ctype.h>
#include <stdio.h>
int main()
{
printf("isalnum('a'): %d\n", isalnum('a'));
printf("isalpha('a'): %d\n", isalpha('a'));
printf("iscntrl('\n'): %d\n", iscntrl('\n'));
printf("isdigit('1'): %d\n", isdigit('1'));
printf("isgraph('a'): %d\n", isgraph('a'));
printf("islower('a'): %d\n", islower('a'));
printf("isprint('a'): %d\n", isprint('a'));
printf("ispunct('.'): %d\n", ispunct('.'));
printf("isspace(' '): %d\n", isspace(' '));
printf("isupper('A'): %d\n", isupper('A'));
printf("isxdigit('a'): %d\n", isxdigit('a'));
printf("isdigit(0x7fffffff): %d\n", isdigit(0x7fffffff));
return 0;
}
在 MacOS 中,这只是为除最后一个结果之外的每个结果打印出1
,这意味着这些函数只是返回逻辑比较的结果。
结果在Linux有点不同:
isalnum('a'): 8
isalpha('a'): 1024
iscntrl('\n'): 2
isdigit('1'): 2048
isgraph('a'): 32768
islower('a'): 512
isprint('a'): 16384
ispunct('.'): 4
isspace(' '): 8192
isupper('A'): 256
isxdigit('a'): 4096
Segmentation fault
这向我暗示 Linux 中使用的库正在从查找表中获取值,并使用与提供的参数对应的位模式来屏蔽它们。 例如, '1'
(ASCII 49)是一个字母数字字符、一个数字、一个可打印字符和一个十六进制数字,因此此查找表中的条目 49 可能等于 8+2018+32768+16384+4096,即 55274 .
这些函数的文档确实提到参数必须具有 unsigned char (0-255) 或 EOF (-1) 的值,因此超出此范围的任何值都会导致该表被越界读取,从而导致分割错误。
由于我仅使用 integer 参数调用isdigit()
function,因此很难将其描述为未定义行为。 我真的认为库函数应该针对这类问题进行强化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.