在C中生成Segfault的最简单的标准符合方式是什么?

mat*_*ath 58 c iso segmentation-fault

我认为问题就是这么说的.涵盖从C89到C11的大多数标准的示例将是有帮助的.我虽然这个,但我猜它只是未定义的行为:

#include <stdio.h>

int main( int argc, char* argv[] )
{
  const char *s = NULL;
  printf( "%c\n", s[0] );
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

正如一些投票要求澄清:我希望有一个程序通常有编程错误(我能想到的最简单的是段错误),这是保证(按标准)中止.这与最小的段错误问题有点不同,它不关心这种保险.

msa*_*sam 100

raise() 可用于引发段错误:

raise(SIGSEGV);
Run Code Online (Sandbox Code Playgroud)

  • @Marco Segfaults由内核检测到.他们发生了.投掷信号只是指示系统按原样播放.段错误并没有真正发生,但系统将其视为确实发生了.仅因为SIGSEGV信号被引发而不会发生段错误.只有在访问不允许进程访问的内存时才会发生段错误.通过调用`raise(SIGSEGV)`没有发生这种无效的内存访问.为了给你一个真实的比喻,如果你在足球比赛中将球队的得分提高1而没有得分,那并不意味着进球得分. (15认同)
  • 嗯,这不会产生分段错误.它只是提出了SIGSEGV信号: - / (11认同)
  • 据我所知,这仍然是实现定义,即标准没有定义本节的确切行为`7.14.2.1.raise函数`指向`7.14.1.1`,它不讨论与分段错误有关的任何事情. (7认同)
  • Segfaults通常由CPU(特别是MMU)检测,而不是内核.特别是,不执行单个内核代码指令来检测它们.CPU当然会跳转到内核代码来处理段错误.`raise(SIGSEGV)`跳转到内核来处理`SIGSEGV`.这是相当可比的. (5认同)
  • @chux:你如何定义分段错误? (2认同)

Sha*_*our 68

分段错误是实现定义的行为.该标准没有定义实现应如何处理未定义的行为,实际上,实现可以优化未定义的行为并且仍然是合规的.需要明确的是,实现定义的行为是标准未指定但行为应该记录的行为.未定义的行为是不可移植或错误的代码,其行为是不可预测的,因此无法依赖.

如果我们看一下C99草案标准 §3.4.3 未定义的行为,它在第1段的条款,定义和符号部分中说明(强调我的未来):

使用不可移植或错误的程序结构或错误数据时的行为,本国际标准不对此要求

并在第2段中说:

注意可能的未定义行为包括完全忽略具有不可预测结果的情况,在转换或程序执行期间以环境特征(有或没有发出诊断消息)的特定文档执行,终止转换或执行(使用发布诊断消息).

另一方面,如果您只是想要在标准中定义的方法在大多数类Unix系统上导致分段错误,那么raise(SIGSEGV)应该实现该目标.严格来说,SIGSEGV虽然定义如下:

SIGSEGV无效访问存储

和§7.14 信号处理<signal.h>说:

实现不需要生成任何这些信号,除非显式调用raise函数.实现还可以指定附加信号和指向不可响应函数的指针,其中宏定义分别以字母SIG和大写字母或SIG_和大写字母219开始.完整的信号集,它们的语义和默认处理是实现定义的 ; 所有信号编号均为正数.


Nik*_* C. 17

该标准仅提及未定义的行为.它对内存分段一无所知.另请注意,产生错误的代码不符合标准.您的代码无法同时调用未定义的行为并且符合标准.

尽管如此,最短的方式,对我们的体系结构分割故障产生这种故障是:

int main()
{
    *(int*)0 = 0;
}
Run Code Online (Sandbox Code Playgroud)

为什么这肯定会产生段错误?因为对内存地址0的访问总是被系统捕获; 它永远不会是有效的访问(至少不是用户空间代码.)

当然,请注意并非所有体系结构都以相同的方式工作.在其中一些,上面根本不会崩溃,而是产生其他类型的错误.或者声明可以非常好,甚至,内存位置0可以访问得很好.这是标准实际上没有定义发生的事情的原因之一.

  • 我使用了用C编程的嵌入式系统,其中地址0的存储器不仅存在,而且必须写入.例如,这是中断向量表的公共位置.写一些像`((unsigned long*)0)[1] =(unsigned long)main;`但是仍然感觉真的,真的,错误. (8认同)
  • 赞成"你的代码不能同时调用未定义的行为并且符合标准",但是`*(volatile int*)0`是恕我直言更安全的赌注. (3认同)
  • 从历史上看,嵌入式系统人员对标准采取了非常务实的观点.最重要的是具体实现,在小型CPU上,实现通常是硬件与语言最自然的映射.毕竟,根深蒂固在C的起源.写入裸机与具有完整库和预期标准兼容性和可移植性的托管环境非常不同. (3认同)
  • @MichaelShopsin:至少在一些68k系统上,也支持写入地址0.例如,Commodore Amiga内核("exec")会在重新启动之前将0x48454C50(ASCII中的"HELP")写入地址0,如果它发现自己如此严重搞砸了它甚至无法显示错误消息(着名的"大师冥想"盒子".然后ROM引导代码将检查此幻数,并在该点显示错误消息.不可否认,所有这些(通常)都是在汇编中编写的内核代码中完成的,但至少在没有MMU的低端Amigas中,原则上任何程序都可以执行. (2认同)

Ker*_* SB 12

正确的程序不会产生段错误.而且您无法描述错误程序的确定性行为.

"分段错误"是x86 CPU所做的事情.你通过尝试以不正确的方式引用内存来获得它.它还可以指内存访问导致页面错误(即尝试访问未加载到页表中的内存)的情况,并且操作系统决定您无权请求该内存.要触发这些条件,您需要直接为您的操作系统和硬件编程.它不是C语言指定的.

  • "分段错误"是几乎任何CPU都可以抛出的东西.实际上它可能是适合的内存管理硬件.作为一个整天在SPARC系统上工作的人,我可以告诉你SPARC上的Solaris很高兴向您抛出一个段错误. (6认同)
  • 您正在描述页面错误,而不是分段错误.他们是非常不同的. (2认同)
  • 分段错误是您尝试访问不允许的内存段的错误.它与x86 CPU无关,与页面错误无关. (2认同)

oua*_*uah 8

如果我们假设我们没有提出信号调用 raise,则分段错误可能来自未定义的行为.未定义的行为是未定义的,并且编译器可以自由拒绝转换,因此在所有实现上都不会保证未定义的答案都会失败.此外,调用未定义行为的程序是错误的程序.

但是,这一个是最短的,我可以得到在该段错误我的系统:

main(){main();}
Run Code Online (Sandbox Code Playgroud)

(我用gcc和编译-std=c89 -O0).

顺便说一下,这个程序真的引用了未定义的bevahior吗?

  • C99 6.5.2.2p11需要支持递归,但在标准中没有任何提及对调用堆栈深度的任何限制(有趣的事实:在C99中从不使用"堆栈"一词).C委员会肯定不打算要求所有符合要求的实现提供*无限*调用堆栈深度,因此我们留下第4节第2段"未指明的行为另有说明......通过省略任何明确的行为定义. " 换句话说:它是未定义的,但不是*明确的*未定义的. (3认同)

The*_*a10 5

 main;
Run Code Online (Sandbox Code Playgroud)

就是这样。

真的。

本质上,它的作用是将其定义main变量。在C中,变量和函数都是符号——内存中的指针,因此编译器不会区分它们,并且这段代码不会抛出错误。

然而,问题在于系统如何运行可执行文件。简而言之,C 标准要求所有 C 可执行文件都内置一个环境准备入口点,这基本上可以归结为“调用main”。

然而,在这种特殊情况下,main是一个变量,因此它被放置在称为 的内存的不可执行.bss部分中,用于变量(而不是.text代码)。尝试执行代码.bss违反了其特定的分段,因此系统会抛出分段错误。

objdump为了说明这一点,这是结果文件的(部分) :

# (unimportant)

Disassembly of section .text:

0000000000001020 <_start>:
    1020:   f3 0f 1e fa             endbr64 
    1024:   31 ed                   xor    %ebp,%ebp
    1026:   49 89 d1                mov    %rdx,%r9
    1029:   5e                      pop    %rsi
    102a:   48 89 e2                mov    %rsp,%rdx
    102d:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1031:   50                      push   %rax
    1032:   54                      push   %rsp
    1033:   4c 8d 05 56 01 00 00    lea    0x156(%rip),%r8        # 1190 <__libc_csu_fini>
    103a:   48 8d 0d df 00 00 00    lea    0xdf(%rip),%rcx        # 1120 <__libc_csu_init>

    # This is where the program should call main
    1041:   48 8d 3d e4 2f 00 00    lea    0x2fe4(%rip),%rdi      # 402c <main> 
    1048:   ff 15 92 2f 00 00       callq  *0x2f92(%rip)          # 3fe0 <__libc_start_main@GLIBC_2.2.5>
    104e:   f4                      hlt    
    104f:   90                      nop

# (nice things we still don't care about)

Disassembly of section .data:

0000000000004018 <__data_start>:
    ...

0000000000004020 <__dso_handle>:
    4020:   20 40 00                and    %al,0x0(%rax)
    4023:   00 00                   add    %al,(%rax)
    4025:   00 00                   add    %al,(%rax)
    ...

Disassembly of section .bss:

0000000000004028 <__bss_start>:
    4028:   00 00                   add    %al,(%rax)
    ...

# main is in .bss (variables) instead of .text (code)

000000000000402c <main>:
    402c:   00 00                   add    %al,(%rax)
    ...

# aaand that's it! 
Run Code Online (Sandbox Code Playgroud)

PS:如果您编译为平面可执行文件,这将不起作用。相反,您将导致未定义的行为。