V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NoahNye
V2EX  ›  C

求解答,关于 C 使用 strcpy 问题

  •  
  •   NoahNye · 2021-01-27 11:15:19 +08:00 · 2020 次点击
    这是一个创建于 1388 天前的主题,其中的信息可能已经有所发展或是发生改变。

    着实无法理解,为什么已经赋值的 c 和 d 会在 strcpy 之后改变原值(这里故意设置错误下标导致复制越界)

    #include <stdio.h>
    #include <string.h>
    int main(){
    
            char a[6]={"hello"},b[7]={"world1"};
            // a[]="hello";
            // b[]="world";
    
            int c,d;
            int *c_,*d_;
    
            c=(int)sizeof(a)/sizeof(*a);
            d=(int)sizeof(b)/sizeof(*b);
            // c_=&c;
            // d_=&d;
    
            // printf("c_=%p\td_=%p\n",c_,d_);
            // printf("c=%p\td=%p\n",&c,&d);
            printf("c=%d,d=%d\n",c,d);
    
            strcpy(a,b);
    
            printf("%s-%s\n",a,b);
            printf("c=%d,d=%d",c,d);
    
    
    
    }
    

    运行结果: !run1 [root@iZwz94s0djlh8ob47gwodcZ Ctest]# ./test
    c=6,d=7
    world1-world1
    c=6,d=0
    run1.png

    并且,在用新的指针赋值之后,这个 c 和 d 又不会因为 strcpy 而改变原值了。

    #include <stdio.h>
    #include <string.h>
    int main(){
    
            char a[6]={"hello"},b[7]={"world1"};
            // a[]="hello";
            // b[]="world";
    
            int c,d;
            int *c_,*d_;
    
            c=(int)sizeof(a)/sizeof(*a);
            d=(int)sizeof(b)/sizeof(*b);
            c_=&c;
            d_=&d;
    
            // printf("c_=%p\td_=%p\n",c_,d_);
            // printf("c=%p\td=%p\n",&c,&d);
            printf("c=%d,d=%d\n",c,d);
    
            strcpy(a,b);
    
            printf("%s-%s\n",a,b);
            printf("c=%d,d=%d",c,d);
    
    
    
    }
    

    运行结果:
    [root@iZwz94s0djlh8ob47gwodcZ Ctest]# ./test
    c=6,d=7
    world1-world1
    c=6,d=7
    run2.png

    测试了 gcc 和 clang,只有 gcc 出现这个问题。

    第 1 条附言  ·  2021-01-27 16:47:03 +08:00

    p.png
    gdb里很明显了,因为越界导致顶栈被设为0。
    想到一个曲线救国的方法,就是再另外设置一个变量 'e',只要数组复制相差的位数小于e,就可以假装显示正常结果,相当于设置一个变量当作溢出缓存,避免顶端想要的结果被修改,不过也只是可用在研究或调试上。
    很低端的错误,让大家见笑了。

    和strcpy一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。如果strlen( src )的值小于len,dst数组就用额外的NUL字节填充到len长度。如果strlen( src )的值大于或等于len,那么只有len个字符被复制到dst中。注意!它的结果将不会以NUL字节结尾。 ——《C和指针》

    14 条回复    2021-01-27 15:24:06 +08:00
    hello2060
        1
    hello2060  
       2021-01-27 11:23:23 +08:00 via iPhone   ❤️ 1
    已经忘了 C 了,但是既然 stripy 越界了,变量值改变也是有可能的啊,你看看数组 a 和 d 的地址,看看 d 是不是跟在 a 数组元素后面
    shuax
        2
    shuax  
       2021-01-27 11:24:51 +08:00
    你不懂什么叫越界吗
    hello2060
        3
    hello2060  
       2021-01-27 11:25:01 +08:00 via iPhone   ❤️ 1
    最简单的,IDE 里单步 debug 看 memory 变化
    hello2060
        4
    hello2060  
       2021-01-27 11:28:50 +08:00 via iPhone   ❤️ 1
    最后你定义了 c_ d_那可能就改变了栈内各个变量地址的关系,本身正常的程序就不预设栈内变量地址之间的关系。一旦越界了,啥都有可能发生。

    不过最简单的还是 debug 一下
    hello2060
        5
    hello2060  
       2021-01-27 11:30:13 +08:00 via iPhone   ❤️ 2
    你说你无法理解,这个不需要理解啊,因为这个 code 出错了,所以啥错误都是有可能的。
    NoahNye
        6
    NoahNye  
    OP
       2021-01-27 11:40:23 +08:00
    @hello2060 谢谢回复,之前有发过在 stackoverflow,好像也差不多得到这种回答,大概是错误的代码导致不可预期的行为,但我还是觉得这个赋值的这两个变量不应该因为 strcpy 而改变,因为它们并没有在 strcpy 之后被重新赋值。既然是程序错误,我也不在这个错误代码里钻牛角尖了。最后再次感谢您的回复。
    LANB0
        7
    LANB0  
       2021-01-27 11:44:31 +08:00   ❤️ 1
    如一楼所说,你 strcpy 越界了。越界的字节覆盖了变量 d 低字节的内存,后一种写法,d_的值也是有误的。
    Linux 下局部变量内存地址分布顺序:
    按字节大小,大的先入栈;
    字节大小相同的,后定义的先入栈;

    按定义顺序,依次入栈的是 c,d,a,b ; intel 小端,d 的低字节和 a 的末尾是连着的;进而溢出的'\0'把 d 的低字节覆盖了
    hello2060
        8
    hello2060  
       2021-01-27 11:55:56 +08:00
    strcpy(a, b) 的时候 a + 6 和 a + 7 那个位置的值都被错误的覆盖了(我已经忘了最后的\0 是怎么处理的了,反正至少有一个 byte 被 strcpy 这个动作错误的改写了,

    d 的内存地址可能刚好包括这个 byte, '这个赋值的这两个变量不应该因为 strcpy 而改变,因为它们并没有在 strcpy 之后被重新赋值' -- 变量 d 并不是只有被重新赋值了才会有新值,任何操作他内存地址的操作都有可能改变他的值啊


    比如说你定义

    char a, char b, 假设他们在内存里是 [a,b]
    你再定义 int* p = &a, (我不确定这个是否是正确的语法)
    *p = 0, 这里对 p 的内存改写,因为 p 指向 4 个 byte 的 int, 从 a 地址开始的 4 个 byte 都变成 0 了,b 也变 0 了
    nightwitch
        9
    nightwitch  
       2021-01-27 11:57:25 +08:00
    继续往下学吧,如果你只停留在 C 语法上的话,大家跟你解释你也不大听得懂。
    这个问题你学到 C 语言的基本类型在机器上的内存排布就自然明白了
    changcui
        10
    changcui  
       2021-01-27 13:13:51 +08:00
    和 strcpy 无关吧,sizeof 是编译的时候就计算好的
    ipwx
        11
    ipwx  
       2021-01-27 13:16:35 +08:00   ❤️ 2
    怎么又讨论起未定义行为了?讨论这种的行为没有意义,因为 -O3 可以把指令都乱序,内存读取跳过(用寄存器),诸如此类的。。。
    huangmingyou
        12
    huangmingyou  
       2021-01-27 13:22:35 +08:00
    gdb 解君愁
    ghostcir
        13
    ghostcir  
       2021-01-27 14:21:12 +08:00
    因为 a 内存越界了,结尾的\0 填到了 d 的内存
    aneostart173
        14
    aneostart173  
       2021-01-27 15:24:06 +08:00
    第一个 c_和 d_都没用,编译器给优化掉了, 第二个 c_和 d_都用上了,栈布局就变了。越界了什么事都会发生,讨论这个没任何意义。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5140 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 03:51 · PVG 11:51 · LAX 19:51 · JFK 22:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.