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

strcat with char pointer to a string literal

只是想了解在最近的采访中询问的以下代码。

#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;
}

执行轨迹:

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

(gdb) p ptr
 = 0x400624 "Linux"
(gdb) p a+1
 = 0x7fffffffe7f1 "olarisLinux"
**(gdb) p a
 = "SolarisL"**
**(gdb) p a+0
 = 0x7fffffffe7f0 "SolarisLinux"**
(gdb)
 = 0x7fffffffe7f0 "SolarisLinux"
**(gdb) p ptr
 = 0x78756e69 <error: Cannot access memory at address 0x78756e69>
(gdb)**

我有几个问题:

  1. 是否 strcat 从原始位置删除字符串文字,因为访问 ptr 会出现分段错误?

  2. 为什么 gdb 中的 p a 没有给出正确的输出,而 p a+0 显示 "SolarisLinux"

如果我理解你的问题是正确的,你知道程序有未定义的行为,因为 a 无法保存字符串 "Solaris" 与 "Linux" 连接。

所以你要找的答案不是"This is undefined behavior"而是:

why is it behaving this way

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

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

但是 - 有时您可以找到针对您的特定系统的解释 - 请记住它是针对您的系统的,绝不是通用的。

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

#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
&ptr = 0x7ffff3ce5058
 ptr = 0x400820

Before strcat
  a:
53 6f 6c 61 72 69 73 00
  ptr:
20 08 40 00 00 00 00 00

After strcat
  a:
53 6f 6c 61 72 69 73 4c
  ptr:
69 6e 75 78 00 00 00 00

SolarisLinux
Segmentation fault

这里的输出添加了一些注释:

   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[=12=]
  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[=12=]

SolarisLinux            // print a
Segmentation fault      // print ptr crashes because ptr doesn't hold a valid pointer value

所以在我的系统上的解释是:

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

所以对于你的具体问题:

1)Does strcat removes the string literal from the original location, as accessing ptr gives a segmentation fault.

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

2)Why does p a in gdb doesn't give the proper o/p where as p a+0 shows "SolarisLinux".

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

嗯,我们这里有一个指针错误。

我会尽量让大家明白:

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

当你这样做时:

char *ptr = "Linux";
char a[] = "Solaris";

您将 ptr 分配给 'L' char 的地址,您在堆栈上获得 8 * sizeof(char) 内存,然后在其中复制 "Solaris[=20=]"

当你连接这两个字符串时,因为你从未创建新的内存space(例如做mallocchar str[50]),要求strcat写在后面为您的函数使用保留的堆栈内存的末尾。这就是导致stackoverflow.

的那种编程错误

此处 gdb 尝试以最佳方式显示字符串。

(gdb) p ptr
 = 0x400624 "Linux"

指向静态字符串区域的指针,正确显示

(gdb) p a+1
 = 0x7fffffffe7f1 "olarisLinux"

如您所愿显示的堆栈指针

(gdb) p a
 = "SolarisL"

指向 8 个字符长度区域的指针,gdb 知道大小,显示第 8 个字符。

(gdb) p a+0
 = 0x7fffffffe7f0 "SolarisLinux"

指向堆栈的指针(gdb 不知道大小,因为您进行指针运算)

(gdb) p ptr
 = 0x78756e69 <error: Cannot access memory at address 0x78756e69>

这个很棘手。看到这里,ptr 和第一次打印时的地址不一样了。有可能你在某个时候写了 ptr 值(因为你在堆栈的某个地方写了你不应该写的)。

1)Does strcat removes the string literal from the original location, as accessing ptr gives a segmentation fault.

不行,原来的位置不能被覆盖。

2)Why does p a in gdb doesn't give the proper o/p where as p a+0 shows "SolarisLinux".

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

如果问题是 "I know it's wrong, but why did it do that?",则有两种回答方式。

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

我知道你问这个问题是真诚的,但我不得不说,这些问题对我来说总是听起来像:"I ran through a busy intersection when the sign said Don't Walk. A blue car ran over me, and I broke my left leg. I don't understand why. Why wasn't I hit by a red truck? Why didn't I break my right arm?"

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

            +----+----+----+----+----+----+----+----+
         a: | S  | o  | l  | a  | r  | i  | s  | [=10=] |
            +----+----+----+----+----+----+----+----+

            +----+----+----+----+
       ptr: | 78 | 56 | 34 | 12 |
            +----+----+----+----+

            +----+----+----+----+----+----+
0x12345678: | L  | i  | n  | u  | x  | [=10=] |
            +----+----+----+----+----+----+

这里我假设字符串 "Linux" 存储在地址 0x12345678,因此 ptr 保存该值。我在想象您的机器使用 32 位指针。 (虽然现在,它可能会使用 64。)我想象你的机器使用 "little endian" 字节顺序,这意味着构成指针 p 的字节以相反的顺序存储在内存中超出您的预期。

您说在调用 strcat 后,a 打印出您期望的连接字符串,但是当您尝试打印 ptr 时程序崩溃了。让我们将 ptr 的打印输出更改为

printf("%p: %s\n", ptr, ptr);

在调用 strcat 之前,这将打印类似

的内容
0x12345678: Linux

但调用 strcat 的实际作用是:

            +----+----+----+----+----+----+----+----+
         a: | S  | o  | l  | a  | r  | i  | s  | L  |
            +----+----+----+----+----+----+----+----+

            +----+----+----+----+
       ptr: | i  | n  | u  | x  | [=13=]
            +----+----+----+----+

现在,ptr 的打印输出将类似于

0x78756e69: Segmentation violation (core dumped)

您覆盖了指针 ptr,因此它不再指向存储字符串 "Linux" 的地址 0x12345678,它现在指向位置 0x78756e69,其中这些十六进制数字来自字符 i n u x。如果您没有访问地址 0x78756e69 的权限,您将遇到崩溃。如果您确实有权访问位置 0x78756e69,您将打印一些垃圾字符串。

现在,综上所述,重要的是要注意这不一定会发生什么。我假设编译器将指针 ptr 存储在内存中的数组 a 之后。这是一种可能性,但显然不是唯一的可能性。如果编译器碰巧将 ptr 存储在其他地方,那么其他内容将被 inux 覆盖,并且其他内容可能会出错。或者什么都不会出错。 (换句话说,你可能会被蓝色的车撞到,或者你可能会被红色的卡车撞到,或者你可能幸运地过马路而没有被撞到。)


附录:我刚刚更仔细地查看了您的 post,我看到 gdb 告诉您 ptr 已更改为 0x78756e69,并且它不能访问那里的内存。但现在我们知道那个奇怪的值 0x78756e69 可能来自哪里。 :-)