簡體   English   中英

strcat用char指向字符串文字

[英]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)**

我有幾個問題:

  1. strcat是否從原始位置刪除字符串文字,因為訪問ptr會產生分段錯誤?

  2. 為什么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的字節以與你預期的相反的順序存儲在內存中。

你說,打完電話后strcata打印出你所期望的連接字符串,但是當你試圖打印程序崩潰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"

當您連接這兩個字符串時,由於您從未創建過新的內存空間(例如,執行mallocchar 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM