c - C-使用指针时出现strncpy段错误

标签 c strncpy

我有以下代码:

#include<stdio.h>
#include <string.h>

int main(void) {
    char *src = "This is my string.";
    char *dest, *ret;
    //char dest[64], *ret;
    ret = strncpy(dest, src, 5);
    size_t s = strlen(ret);

    printf("src: %s\n", src);
    printf("dst: %s|\n", dest);
    printf("ret: %s|\n", ret);
    printf("len: %d\n", s);

    //for (int i = 0; i < 5; i++) {
    //    printf("i: %d\n", i);
    //}

    return 0;
}


for循环已禁用

$ gcc -g -o test test.c; ./test 
src: This is my string.
dst: This |
ret: This |
len: 5


for循环已启用

$ gcc -g -o test test.c; ./test 
Segmentation fault (core dumped)


我不知道为什么仅在启用for循环时这才失败。

这是否是未定义的行为,因为我在dest参数中使用了悬空指针,或者对此有另一种解释?

通过查看gdb会话,尝试将ecx的值分配给rdi寄存器时崩溃了吗?

(gdb) bt
#0  0x00007ffff7f4a1a7 in __strncpy_avx2 () from /lib64/libc.so.6
#1  0x000000000040116e in main () at stack.c:8
(gdb) x/i 0x00007ffff7f4a1a7
=> 0x7ffff7f4a1a7 <__strncpy_avx2+1591>:    mov    DWORD PTR [rdi],ecx
(gdb) x/i $rdi
0x401060 <_start>:  endbr64
(gdb) p $rdi
$7 = 4198496
(gdb) p $ecx
$8 = 1936287828

最佳答案

每个人都会听到的关于规范的答案是这样的:该程序崩溃是因为您通过写入未初始化的指针来调用UB。在这一点上,崩溃是一种有效的行为,因此有时会崩溃,有时还会执行其他同样有效的操作(因为UB)。

这是正确的,但不能回答您的问题。您的问题是:“为什么在所有情况下都不会崩溃?”在您的情况下,只有在更改程序结构以包含似乎执行不相关行为的for循环时,才实现段错误。为此,我们需要对程序存储器的布局和段错误的性质进行基本介绍,我们将从段错误开始。

分段错误和虚拟内存

如果您不熟悉CPU架构,那么分段错误就是一个复杂的问题。它的目的非常简单,如果执行过程尝试访问不应访问的内存,则应发出段错误。细节中的魔鬼是什么,定义了“进程不应该接触的内存”?分段错误应如何传达给操作系统?

在现代操作系统和CPU体系结构上,使用virtual memory system控制进程的有效内存空间。虚拟内存的操作不在您的问题范围内,但是足以说明操作系统和CPU本身都知道进程可以访问和不能访问的地址。如果您的进程超出了其允许的内存空间范围,则会发出段错误。

要“发出”段错误,CPU将synchronously interrupt您的程序,并警告操作系统您做了一件顽皮的事情。这些也称为“异常”或“陷阱”,但是它们都是“您的程序要求CPU做它无法或不会做的事情”的不同命名法。操作系统处理该中断,然后向程序发出信号(* Nix)或异常(Win32)。如果您的程序尚未为该信号/异常设置处理程序,则操作系统会正常崩溃。

关于虚拟内存的一个有趣的oolie是,它通常仅以2 ^ 12个连续字节(4KiB)的包发行。因此,即使您的进程只想说10个字节,也要至少处理4KiB。字节的这种连续分组称为“页”,因为它将存储器的“行”分组。

程序存储器和堆栈

即使您的进程从不使用malloc或类似名称请求内存,它也将获得几页内容以实现所谓的the stack(将其名称提供给某些网站)。这是您本地声明的变量(如srcdestrets)的位置。在函数调用之间移动时,它还用于溢出非易失性CPU寄存器,但这也不在范围之内。

因此,如果dest只是堆栈上的一块内存,并且从未在程序中初始化过,它指向什么?好了,该内存地址上随机存在的任何随机数据现在就是您的指针。现在,程序的操作处于堆栈页面中垃圾字节的突发状态。

结论

如果堆栈空间中的垃圾恰好指向了为您的进程发布的用于堆栈空间的内存页面之一内的某个位置,则您的进程将不会访问无效的内存,并且会不断变化(或者它指向附近的某个位置,Linux可以自动如果您位于最后一个有效页面的一页之内,则增加堆栈数量)。但是,如果它指向其他任何地方,则会导致无效的内存访问,并且CPU会警告相关权限。您的过程是犯罪的,将受到相应的对待。

你要说:“但是,镍镍铁合金与for循环有什么关系?”没什么,for循环是红色鲱鱼。在这种情况下,恰好是将堆栈分配偏向垃圾碰巧导致段错误的地方。这可能与许多事情有关,可能是ASLR的结果,或者仅仅是偶然的偶然事件。比我更了解虚拟内存实现的人可能会对此有所了解。

勘误

现在,您程序的结构中也有一个(我认为)意外的错误,这使问题很恼人。您使用以下命令执行初始字符串复制:

ret = strncpy(dest, src, 5);


不会以空字符串结尾的目标字符串,这意味着在您调用时:

size_t s = strlen(ret);


strlen将继续读取,直到命中一个空字节。因此,即使dest碰巧指向了有效的位置,内存垃圾带来的厄运也会导致strlen读入无效内存。

关于c - C-使用指针时出现strncpy段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57396537/

相关文章:

c - 如何在 Arduino 中串行可用时退出 'for' 循环

将 C 语言转换为 MIPS 汇编语言

将由空格分隔的数字字符串转换为 int 数组

c - 不工作/使用自己的 Strncpy 函数

c - strcpy如何改变string的值

c - 将 .a 库链接到 .o 对象,因此在构建时只需要包含 .o

c - 这个程序中 sockaddr_in 的目的是什么?

c++ - gcc-8 -Wstringop-truncation 什么是好的做法?

c - C 中带有指针的 string.h 和 strncpy

c - 使用 strncpy。 Valgrind 抛出无效读取