[英]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
$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
會產生分段錯誤?
為什么pa
在gdb中沒有給出正確的輸出,而p a+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
&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中的pa沒有給出正確的o / p,而p a + 0顯示“SolarisLinux”。
這是猜測 - 僅此而已。 我的猜測是,GDB知道, a
是8個字節,因此打印a
直接打印僅8個字節。 當打印a + 0
mys猜測是gdb看到a + 0
像指針(因此無法知道對象大小)所以gdb保持打印直到它看到零終止。
如果問題是“我知道這是錯的,但為什么會這樣 ?”,也有幾分回答這兩種方式。
(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
,所以它不再指向存儲字符串"Linux"
地址0x12345678
,它現在指向位置0x78756e69
,其中這些十六進制數字來自字符inux
。 如果您無權訪問地址0x78756e69
,則會發生崩潰。 如果您確實有權訪問位置0x78756e69
,則會打印一些垃圾字符串。
現在,盡管如此,重要的是要注意這不一定會發生什么。 我假設編譯器將指針ptr
存儲在內存中的數組a
之后。 這是一種可能性,但顯然不是唯一的可能性。 如果編譯器碰巧將ptr
存儲在其他地方,那么其他東西會被inux
覆蓋,而其他東西可能會出錯。 或者沒有什么可能出錯。 (換句話說,你可能會受到藍色汽車的撞擊,或者你可能會受到紅色卡車的撞擊,或者你可能會幸運地穿過街道而不會受到任何打擊。)
附錄:我剛剛仔細查看了你的帖子,我看到gdb告訴你ptr
已經改為0x78756e69
,並且它無法訪問那里的內存。 但現在我們知道0x78756e69
可能來自哪個奇怪的值。 :-)
好吧,這里我們有一個指針錯誤。
我會試着理解:
常量字符串(如"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個char len區域的指針,gdb知道大小,顯示8個第一個char。
(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中的pa沒有給出正確的o / p,而p a + 0顯示“SolarisLinux”。
它是一個調試器,編寫它是為了避免某種類型的錯誤,所以當它可以時,他只讀取應該是紅色的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.