这是很正常的行为呀,看汇编(x86_64)(希望没弄错什么):
# 初始化 sz,8+2 个 0,占用 -32(%rbp) 到 -23(%rbp)
movq $0, -32(%rbp)
movw $0, -24(%rbp)
movl $.LC1, %edi
call puts
# 将 sz 的地址放入 %rax 中
leaq -32(%rbp), %rax
# 复制字符串中...
movabsq $7596482334399685992, %rdx
movq %rdx, (%rax)
movabsq $7812730951471755639, %rcx
movq %rcx, 8(%rax)
movabsq $2318352665872703599, %rbx
movq %rbx, 16(%rax)
movabsq $7596482334399685992, %rdx
movq %rdx, 24(%rax)
movw $29047, 32(%rax)
movb $0, 34(%rax)
# 复制字符串完成,覆盖 -32(%rbp) 到 1(%rbp)。-23(%rbp) 到 1(%rbp) 的数据被覆盖,这里包括了函数返回地址等数据
所以直到 main 函数返回时才会发生内存访问违例,导致 SIGSEGV 信号的发送。硬件才不在乎你的访问有没有越界呢,它只关心你有没有权限访问指定的内存地址。
另外,严格来讲,在信号处理函数里是不能调用 printf 等会导致内存分配的函数的,因为信号处理是异步的,你需要假设它在任意时候都可能被执行。如果你的程序正好在执行 malloc 之类的函数,锁住了 C 标准库的内存分配用的数据结构,你再次使用需要这个锁的函数就会导致死锁。所以信号处理函数中只能调用
又,你要处理 SIGSEGV 信号通常得使用另外分配的栈来处理,因为已有的函数栈已经坏掉不能用了。当然很少会有程序去费心思处理 SIGSEGV 的,最多记录一些日志而已。