具有指向字符串文字的char指针的strcat

具有指向字符串文字的char指针的strcat,c,string-literals,strcat,C,String Literals,Strcat,在最近的一次采访中,我只是想理解下面的代码 #include <stdio.h> #include <string.h> int main() { char *ptr = "Linux"; char a[] = "Solaris"; strcat(a, ptr); printf("%s\n", ptr); printf("%s\n", a); return 0; } #包括 #包括 int main(){ char*pt

在最近的一次采访中,我只是想理解下面的代码

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

int main() {
    char *ptr = "Linux";
    char a[] = "Solaris";
    strcat(a, ptr);
    printf("%s\n", ptr);
    printf("%s\n", a);
    return 0;
}
#包括
#包括
int main(){
char*ptr=“Linux”;
字符a[]=“Solaris”;
strcat(a,ptr);
printf(“%s\n”,ptr);
printf(“%s\n”,a);
返回0;
}
执行跟踪:

gcc -Wall -g prog.c
gdb a.out

(gdb) p ptr
$15 = 0x400624 "Linux"
(gdb) p a+1
$20 = 0x7fffffffe7f1 "olarisLinux"
**(gdb) p a
$21 = "SolarisL"**
**(gdb) p a+0
$22 = 0x7fffffffe7f0 "SolarisLinux"**
(gdb)
$23 = 0x7fffffffe7f0 "SolarisLinux"
**(gdb) p ptr
$24 = 0x78756e69 <error: Cannot access memory at address 0x78756e69>
(gdb)**
gcc-Wall-g prog.c
gdb a.out
(gdb)p ptr
$15=0x400624“Linux”
(gdb)PA+1
$20=0x7FFFFFE7F1“olarisLinux”
**(gdb)p a
$21=“日光浴”**
**(gdb)PA+0
$22=0x7FFFFFE7F0“SolarisLinux”**
(gdb)
$23=0x7FFFFFE7F0“SolarisLinux”
**(gdb)p ptr
$24=0x78756e69
(gdb)**
我有几个问题:

  • strcat
    是否从原始位置删除字符串文字,因为访问
    ptr
    会导致分段错误

  • 为什么gdb中的
    PA
    没有给出正确的输出,而as
    PA+0
    显示
    “SolarisLinux”


  • 如果我理解您的问题是正确的,您就会知道,由于
    a
    无法保存字符串“Solaris”与“Linux”连接,程序具有未定义的行为

    因此,您寻找的答案不是“这是未定义的行为”,而是:

    为什么会这样

    在处理未定义的行为时,我们不能对发生的事情给出一般性的解释。它可以在不同的系统上执行不同的操作,或者为不同的编译器(或编译器版本)执行不同的操作,等等

    因此,人们常说,试图用未定义的行为解释程序中发生的事情是没有意义的。没错

    然而,有时你可以找到一个解释你的特定系统-只要记住,它是特定于你的系统,并没有任何方式的普遍性

    因此,我更改了您的代码以添加一些调试打印:

    #include<stdio.h>
    #include<string.h>
    
    int main()
    {
        char *ptr = "Linux";
        char a[] = "Solaris";
        printf("   a = %p\n", (void*)a);
        printf("&ptr = %p\n", (void*)&ptr);
        printf(" ptr = %p\n", (void*)ptr);
    
        // Print the data that ptr holds
        unsigned char* p = (unsigned char*)&ptr;
    
        printf("\nBefore strcat\n");
        printf("  a:\n");
        for (int i = 0; i < 8; ++i) printf("%02x ", *(a+i));
        printf("\n");
    
        printf("  ptr:\n");
        for (int i = 0; i < 8; ++i) printf("%02x ", *(p+i));
        printf("\n");
    
        strcat(a,ptr);
    
        printf("\nAfter strcat\n");
        printf("  a:\n");
        for (int i = 0; i < 8; ++i) printf("%02x ", *(a+i));
        printf("\n");
    
        printf("  ptr:\n");
        for (int i = 0; i < 8; ++i) printf("%02x ", *(p+i));
        printf("\n\n");
    
        printf("%s\n", a);
    
        printf("%s\n", ptr);
    
        return 0;
    }
    
    此处是添加了一些注释的输出:

       a = 0x7ffff3ce5050   // The address where the array a istored
    &ptr = 0x7ffff3ce5058   // The address where ptr is stored. Notice 8 higher than a
     ptr = 0x400820         // The value of ptr
    
    Before strcat
      a:
    53 6f 6c 61 72 69 73 00 // Hex dump of a gives Solaris\0
      ptr:
    20 08 40 00 00 00 00 00 // Hex dump of ptr is the value 0x0000000000400820 (little endian system)
    
    // Here strcat is executed
    
    After strcat
      a:
    53 6f 6c 61 72 69 73 4c // Hex dump of a gives SolarisL
      ptr:
    69 6e 75 78 00 00 00 00 // Ups.. ptr has changed! It's not a valid pointer value anymore
                            // As a string it is inux\0
    
    SolarisLinux            // print a
    Segmentation fault      // print ptr crashes because ptr doesn't hold a valid pointer value
    
    因此,在我的系统中,解释是:

    a
    位于
    ptr
    之前的内存中,因此当
    strcat
    写入
    a
    的范围外时,它实际上会覆盖
    ptr
    的值。因此,当试图将
    ptr
    用作有效指针时,程序崩溃

    因此,对于您的具体问题:

    1) strcat是否从原始位置删除字符串文字,因为访问ptr会导致分段错误

    不可以。被覆盖的是
    ptr
    的值。sring文字很可能未被触及

    2) 为什么gdb中的PA没有给出正确的o/p,因为PA+0显示“SolarisLinux”


    这只是一个猜测——仅此而已。我猜gdb知道
    a
    是8个字节,所以直接打印
    a
    只打印8个字节。当打印
    a+0
    时,我猜测gdb将
    a+0
    视为指针(因此无法知道对象大小),所以gdb会一直打印,直到看到零终止。

    好吧,这里我们有一个指针错误

    我会尽量让人理解:

    常量字符串(如
    “Linux”
    “Solaris”
    )存储在程序的特定内存区域中。对于您的程序,在其他字符串(例如错误消息)中,应该有一个区域带有:
    “Linux\0Solaris\0%s\n\0%s\n\0”

    当您这样做时:

    char *ptr = "Linux";
    char a[] = "Solaris";
    
    您将ptr分配给
    'L'
    字符的地址,并在堆栈上为您提供8*sizeof(char)内存,然后复制
    “Solaris\0”

    当您连接这两个字符串时,由于您从未创建过新的内存空间(例如执行
    malloc
    char str[50]
    ),请
    strcat
    在为您的函数使用保留的堆栈内存结束后写入。这就是导致堆栈溢出的编程错误

    在这里,gdb尽量显示字符串

    (gdb) p ptr
    $15 = 0x400624 "Linux"
    
    指向静态字符串区域的指针,正确显示

    (gdb) p a+1
    $20 = 0x7fffffffe7f1 "olarisLinux"
    
    指向按预期显示的堆栈的指针

    (gdb) p a
    $21 = "SolarisL"
    
    指向8个字符区域的指针,gdb知道大小,显示8个字符

    (gdb) p a+0
    $22 = 0x7fffffffe7f0 "SolarisLinux"
    
    指向堆栈的指针(gdb不知道大小,因为您使用指针算法)

    (gdb)p ptr
    $24=0x78756e69
    
    这个很棘手。请看这里,ptr的地址与您第一次打印时的地址不同。有一种可能是您在某个点写入了ptr值(正如您在堆栈上不应该写入的地方写入的那样)

    1) strcat是否从原始位置删除字符串文字,因为访问ptr会导致分段错误

    不,无法覆盖原始位置

    2) 为什么gdb中的PA没有给出正确的o/p,因为PA+0显示“SolarisLinux”

    它是一个调试器,编写它是为了避免某种类型的错误,因此当它可以时,他只读取应该是红色的内容。

    如果问题是“我知道它错了,但它为什么这样做?”,有两种回答方法

    (1) 未定义的行为意味着任何事情都可能发生。使用大小为8的数组并向其中写入13个字符是一件非常错误的事情。您正在覆盖五个字节的内存,这些内存可能用于其他用途,因此覆盖它们意味着。。。任何事情都有可能发生。(但现在我重复我自己。)

    我知道你问这个问题很诚恳,但我不得不说,对我来说,这些问题听起来总是像:“当路牌上写着“不要走”时,我穿过了一个繁忙的十字路口。一辆蓝色的汽车从我身上碾过,我的左腿骨折了。我不明白为什么。为什么我没有被一辆红色卡车撞到?为什么我的右臂没有骨折?”

    (2) 让我们看看为该程序分配的内存的可能布局:

                +----+----+----+----+----+----+----+----+
             a: | S  | o  | l  | a  | r  | i  | s  | \0 |
                +----+----+----+----+----+----+----+----+
    
                +----+----+----+----+
           ptr: | 78 | 56 | 34 | 12 |
                +----+----+----+----+
    
                +----+----+----+----+----+----+
    0x12345678: | L  | i  | n  | u  | x  | \0 |
                +----+----+----+----+----+----+
    
    这里我想象字符串
    “Linux”
    存储在地址
    0x12345678
    ,因此
    ptr
    保存该值。我想象你的机器使用32位指针。(不过现在,它很可能使用64位。)我想象你的机器使用的是“小终端”
    (gdb) p ptr
    $24 = 0x78756e69 <error: Cannot access memory at address 0x78756e69>
    
                +----+----+----+----+----+----+----+----+
             a: | S  | o  | l  | a  | r  | i  | s  | \0 |
                +----+----+----+----+----+----+----+----+
    
                +----+----+----+----+
           ptr: | 78 | 56 | 34 | 12 |
                +----+----+----+----+
    
                +----+----+----+----+----+----+
    0x12345678: | L  | i  | n  | u  | x  | \0 |
                +----+----+----+----+----+----+
    
    printf("%p: %s\n", ptr, ptr);
    
    0x12345678: Linux
    
                +----+----+----+----+----+----+----+----+
             a: | S  | o  | l  | a  | r  | i  | s  | L  |
                +----+----+----+----+----+----+----+----+
    
                +----+----+----+----+
           ptr: | i  | n  | u  | x  | \0
                +----+----+----+----+
    
    0x78756e69: Segmentation violation (core dumped)