繁体   English   中英

为什么这会导致核心转储?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM