strcat,其中char指向字符串文字

时间:2018-08-08 11:55:57

标签: 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;
}

执行跟踪:

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)**

我有几个问题:

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

  2. 为什么gdb中的p ap a+0显示"SolarisLinux"的地方不能提供正确的输出?

3 个答案:

答案 0 :(得分:2)

如果我理解您的问题是正确的,您将意识到该程序具有未定义的行为,原因是a无法保存与“ Linux”连接的字符串“ Solaris”。

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

  

为什么会这样

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

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

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

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

#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\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中的p a不能给出正确的o / p,而p a + 0显示为“ SolarisLinux”。

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

答案 1 :(得分:1)

如果问题是“我知道这是错误的,但为什么会这样做 ?”,则有两种回答方法。

(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。)我想象您的计算机使用“小尾数”字节顺序,这意味着构成指针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  | \0
            +----+----+----+----+

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

0x78756e69: Segmentation violation (core dumped)

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

现在,尽管如此,重要的是要注意,这不是不必要会发生的事情。我假设编译器将指针ptr存储在数组a之后。那是一种可能性,但显然不是唯一的可能性。如果编译器碰巧将ptr存储在其他位置,则inux会覆盖其他内容,并且可能会出错。否则可能不会出错。 (换句话说,您可能会被蓝色的汽车撞到,或者您可能被红色的卡车撞到,或者可能很幸运,穿过街道却完全没有被撞到。)


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

答案 2 :(得分:0)

好吧,这是一个指针错误。

我会努力让自己易于理解:

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

操作时:

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

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

当连接这两个字符串时,由于从未创建过新的存储空间(例如,执行mallocchar 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 <error: Cannot access memory at address 0x78756e69>

这很棘手。请参阅此处,ptr与第一次打印时没有相同的地址。您有可能在某个时候重写了ptr值(因为您在堆栈中某个不应该写的地方)。

  

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

不是,原始位置不能被覆盖。

  

2)为什么gdb中的p a不能给出正确的o / p,而p a + 0显示为“ SolarisLinux”。

这是一个调试器,其编写是为了避免某种类型的错误,因此,在可能的情况下,他只会读取应为红色的内容。