我最近開始用C語言編程一段時間後再次用C語言編程,我對指針的理解有點生疏。引用超出範圍的char *
我想問一下爲什麼這個代碼不會導致任何錯誤:
char* a = NULL;
{
char* b = "stackoverflow";
a = b;
}
puts(a);
我認爲這是因爲b
出去的範圍,a
應該引用不存在的內存位置,因此它們會撥打printf
時出現運行時錯誤。
我在MSVC中運行這個代碼約20次,沒有顯示錯誤。
我最近開始用C語言編程一段時間後再次用C語言編程,我對指針的理解有點生疏。引用超出範圍的char *
我想問一下爲什麼這個代碼不會導致任何錯誤:
char* a = NULL;
{
char* b = "stackoverflow";
a = b;
}
puts(a);
我認爲這是因爲b
出去的範圍,a
應該引用不存在的內存位置,因此它們會撥打printf
時出現運行時錯誤。
我在MSVC中運行這個代碼約20次,沒有顯示錯誤。
在b
被定義的範圍內,它被分配了一個字符串文字的地址。這些文字通常位於內存的只讀部分,而不是堆棧。
當你做你a=b
的b
的值分配給a
,即a
現在包含一個字符串的地址。該地址在b
超出範圍後仍然有效。
如果您採取了地址的b
,然後試圖取消引用該地址,那麼您將調用undefined behavior。
所以,你的代碼是有效的,不不調用不確定的行爲,但下列情況:
int *a = NULL;
{
int b = 6;
a = &b;
}
printf("b=%d\n", *a);
另一個更微妙的例子:
char *a = NULL;
{
char b[] = "stackoverflow";
a = b;
}
printf(a);
這個例子和你之間的區別是b
,這是一個數組,衰退當指向a
時指向第一個元素。所以在這種情況下,a
包含一個局部變量的地址,然後超出範圍。
編輯:
作爲一個側面說明,這是不好的做法,傳遞變量作爲printf
第一個參數,因爲這可能導致format string vulnerability。最好使用字符串常量如下:
printf("%s", a);
或者更簡單地說:
puts(a);
感謝您的解釋和例子;真的幫助:) – MattMatt2000
@CometEngine很高興我能幫上忙。如果您覺得它有用,請隨時[接受此答案](https://stackoverflow.com/help/accepted-answer)。 – dbush
當然:) – MattMatt2000
一行行,這是你的代碼做什麼:
char* a = NULL;
a
是一個指針不引用任何內容(設置爲NULL
)。
{
char* b = "stackoverflow";
b
是一個指針引用靜態的,恆定字符串文字"stackoverflow"
。
a = b;
a
被設置爲也引用靜態的,恆定字符串文字"stackoverflow"
。
}
b
超出範圍。但由於a
是而不是引用b
,那麼這並不重要(它只是引用相同的靜態,常量字符串文字作爲引用b
)。
printf(a);
打印靜態常量字符串字面"stackoverflow"
通過a
引用。
空指針不「指向NULL」。它*是* NULL。 –
@凱特湯普森你說得對。固定。 – SiggiSv
字符串文字是靜態分配的,所以指針無限期地有效。如果你說char b[] = "stackoverflow"
,那麼你會在堆棧中分配一個char數組,當範圍結束時將會變爲無效。這種差異也顯示爲修改字符串:char s[] = "foo"
堆棧分配一個字符串,你可以修改,而char *s = "foo"
只給你一個指向一個字符串的指針,可以放在只讀內存中,所以修改它是未定義的行爲。
其他人解釋說這段代碼是完全有效的。這個答案是關於你的期望,如果代碼已經無效,那麼調用printf
時就會出現運行時錯誤。不一定如此。
讓我們來看看在你的代碼這一變化,這是無效:
#include <stdio.h>
int main(void)
{
int *a;
{
int b = 42;
a = &b;
}
printf("%d\n", *a); // undefined behavior
return 0;
}
這一方案不確定的行爲,但它發生是相當有可能會,事實上,印刷品42 ,由於幾個不同的原因 - 許多編譯器將爲main
的整個主體分配b
的堆棧槽,因爲沒有其他需要空間並且最小化堆棧調整的次數簡化了代碼生成;即使編譯器正式釋放堆棧槽,數字42可能仍然保留在內存中,直到其他內容覆蓋它爲止,並且在a = &b
和*a
之間沒有任何內容可以這樣做;標準優化(「常量和副本傳播」)可以消除這兩個變量,並將*a
的最後一個已知值直接寫入printf
語句(就像您寫入printf("%d\n", 42)
一樣)。
理解「未定義的行爲」並不意味着「該程序會崩潰可預測」是絕對重要的。這意味着「任何事情都可能發生」,並且任何包括看起來像編程人員可能打算工作(在這臺計算機,與這個今天編譯器)似乎工作。
最後一點,沒有一個積極的調試工具我有(Valgrind的,牙山,UBSan)跟蹤「自動」足夠詳細,以便捕獲該錯誤變量壽命方便,但海灣合作委員會6確實產生這種有趣的警告:
$ gcc -std=c11 -O2 -W -Wall -pedantic test.c
test.c: In function ‘main’:
test.c:9:5: warning: ‘b’ is used uninitialized in this function
printf("%d\n", *a); // undefined behavior
^~~~~~~~~~~~~~~~~~
我相信這裏發生的事情是,它沒有我上述優化 - 複製的b
最後已知值到*a
再進printf
- 但它的「最後已知值」爲b
是一個「這個變量是未初始化的」哨兵而不是42.(然後生成代碼相當於printf("%d\n", 0)
。)
字符串常量總是靜態分配和程序可以隨時存取,
char* a = NULL;
{
char* b = "stackoverflow";
a = b;
}
printf(a);
這裏存儲到字符串字面量計算器是由編譯器與分配它將內存分配給int/char變量或指針
區別在於字符串文字是READONLY節/段中的位置。 變量b在堆棧中分配,但它保存只讀段/段的內存地址。
在代碼var'b'中有字符串文字的地址。即使當b失去其範圍爲字符串文字的記憶將永遠被分配
注:分配給字符串文字記憶是二進制組成部分,一旦程序被卸載
參考ELF二進制規範更多的瞭解都將被刪除詳情
的代碼,因爲你只是指定字符指針b
另一個字符指針a
不會產生任何錯誤,這是完全正常的。
在C中,您可以將指針引用分配給另一個指針。這裏實際上字符串「stackoverflow」被用作文字,並且該字符串的基地址位置將被分配給a
變量。
雖然您超出了變量b
的範圍,但仍然使用指針a
完成了任務。所以它會打印結果沒有任何錯誤。
我認爲,作爲以前答案的證明,最好看看代碼中真正存在的內容。人們已經提到字符串文字位於.text部分。所以,他們(文字)總是在那裏。您可以輕鬆地找到的這個代碼
#include <string.h>
int main() {
char* a = 0;
{
char* b = "stackoverflow";
a = c;
}
printf("%s\n", a);
}
使用以下命令
> cc -S main.c
內主。是你會發現,在最底層
...
...
...
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "stackoverflow"
L_.str.1: ## @.str.1
.asciz "%s\n"
你可以閱讀更多關於彙編部分(例如)在這裏:https://docs.oracle.com/cd/E19455-01/806-3773/elf-3/index.html
在這裏,你可以找到的Mach-O可執行文件的非常充分的準備報道: https://www.objc.io/issues/6-build-tools/mach-o-executables/
謝謝:)我也看過msvc反彙編窗口;似乎是同樣的事情 – MattMatt2000
這不是未定義的。字符串文字是靜態分配的。這段代碼非常好。 –
換句話說,'b'已經超出了範圍,但沒有指向它。 –
OTOH,如果你改爲:'char'['s','t','a','c','k','o','v','e' ,'r','f','l','o','w','\ 0'}'。 –