只是想了解最近一次采访中要求的以下代码。
#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)**
我有几个问题:
strcat
是否会从原始位置删除字符串文字,因为访问ptr
会产生分段错误?
为什么gdb中的p a
在p a+0
显示"SolarisLinux"
的地方不能提供正确的输出?
答案 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"
。
当连接这两个字符串时,由于从未创建过新的存储空间(例如,执行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 <error: Cannot access memory at address 0x78756e69>
这很棘手。请参阅此处,ptr与第一次打印时没有相同的地址。您有可能在某个时候重写了ptr值(因为您在堆栈中某个不应该写的地方)。
1)strcat是否从原始位置删除了字符串文字,因为访问ptr会产生分段错误。
不是,原始位置不能被覆盖。
2)为什么gdb中的p a不能给出正确的o / p,而p a + 0显示为“ SolarisLinux”。
这是一个调试器,其编写是为了避免某种类型的错误,因此,在可能的情况下,他只会读取应为红色的内容。