具有指向字符串文字的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
会导致分段错误PA
没有给出正确的输出,而asPA+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)