我試圖弄清楚scanf是否可以通過將資料型別隱藏在void*后面,并將其轉換為%d應該有的含義(即,int*)來理解純粹基于格式指定器的%d的資料型別:
#include <stdio.h>/span>
#include <stdint.h>
int main()
{
int8_t a, b;
void *v[2] = { &a, &b };
sscanf("-111,9
", "%d,%d", (int*)v[0], (int*)v[1])。
printf("Works : %d, %d
", a, b)。)
printf("Does not: %d, %d
", *(int*)v[0], *(int*)v[1]) 。
return 0。
下面是輸出:
Works : -111, 9。
不't: 0, 9.
問題:
- 為什么
scanf()在投到int*的時候可以讀到一個8位的型別,這是由a和b的直接printf驗證的? 它不應該超限嗎? - 相反,為什么
printf()不能列印被解讀為*(int*)v[0]的8位型別,而scanf可以讀入它們? - 是否有一些編譯器時間的魔法來告訴scanf/printf資料型別是什么,因為格式指定無疑是不充分的?
我知道這段代碼可能是錯誤的,但我仍然對這個例子背后的細節感到好奇。
感謝您的幫助!
uj5u.com熱心網友回復:
TL;DR:做一些未定義的行為并不保證會導致崩潰或診斷。 它可能看起來作業。
為什么scanf()在被投到int*的時候可以讀到一個8位的型別,這可以通過a和b的直接printf來驗證?難道它不應該超限嗎?
它確實超限了 -- 你正在將(可能是)4 位元組的值寫入 1 位元組的空間,所以你在后續的 3 位元組中被忽略了。 問題是,像這樣的超范圍寫入是未定義的行為,它可能會崩潰,也可能不會,而且可能看起來還能作業。 最有可能的是,它們只是破壞了某個地方的東西,從而導致后來的一些代碼神秘地崩潰,這很可能就是這里發生的事情(printf 呼叫崩潰或行為不當是因為
v陣列中的資料被 scanf 呼叫破壞了)。如果我注釋掉第二行
printf并在 linux 上用 gcc 編譯這個程式,它會給我:$ ./test 作業。-111, 9. *** stack 探測到粉碎 ***: ./test 終止 Aborted (core dumped)這完全是一致的 -- 帶有錯誤指標的scanf導致了未定義的行為,直到后來的代碼試圖做一些事情(在這種情況下,當main回傳并試圖清理其堆疊框架)才表現出來。
uj5u.com熱心網友回復:
上面的評論回答了這個問題,下面是總結:
scanf不知道它的引數型別是什么。 - @Diasscanf("-111,9 ", "%d,%d", &a, &b); 如果你啟用了編譯器警告,確實會有編譯器警告。 @RaymondChen 程式員有責任確保格式指定器與引數匹配。如果不是,就會出現未定義的行為。- @GarrGodfrey "但是scanf如何將32位的int ("%d")寫入8位的型別而不破壞堆疊?" 要回答這個問題,你需要看一下匯編代碼。確定堆疊是如何布局的,看看什么東西被打碎了。因為有些東西是被粉碎了,但它一定不是什么重要的東西。用-S編譯來查看程式集,或者用除錯器來檢查程式集。- @user3386109 uj5u.com熱心網友回復:
為什么scanf()在投到int*的時候可以讀到一個8位的型別,這可以通過直接列印a和b來驗證?它不應該超限嗎?但是它確實超限了,而且
a和b最終包含了正常的值,這不是任何一種 "驗證"!我對你的程式進行了這樣的修改:
int8_t x1 = 11; int8_t x2 = 22; int8_t x3 = 33; int8_t a; int8_t x4 = 44; int8_t x5 = 55; int8_t x6 = 66; int8_t b; int8_t x7 = 77; int8_t x8 = 88; int8_t x9 = 99; void *v[2] = { &a, &b }; printf("before: %d %d %d %d %d %d %d %d ", x1, x2, x3, x4, x5, x6, x7, x8, x9)。) sscanf("-111,9 ", "%d,%d", (int*)v[0], (int*)v[1])。 printf("Works : %d, %d ", a, b)。) printf(" after: %d %d %d %d %d %d %d %d ", x1, x2, x3, x4, x5, x6, x7, x8, x9)。)當我運行它時,我得到這樣的輸出:
before: 11 22 33 44 55 66 77 88 99 作品 : -111, 9. 后。-1 -1 1 0 0 0 77 88 99毫不奇怪,大部分的
x都被砸了。uj5u.com熱心網友回復:
我試圖弄清楚
scanf是否能夠理解%d的資料型別,純粹基于格式指定器,將資料型別隱藏在void*后面,并將其轉換為%d應有的含義(即,int*)閱讀規范如何? 或者至少是手冊頁? 雖然說實驗是有意義的,但僅從實驗中你能得到的最好的希望就是了解你的特定實作是如何做特定的事情。 依靠良好的檔案作為實驗的基礎,可以讓你有更堅實的基礎。
檔案將支持這樣的結論:
scanf完全依賴于出現在其格式字串中的欄位指令來判斷第二和后續引數的型別。 如果您傳遞的引數型別不正確,那么就會產生未定義的行為。scanf期望對應于%d指令的引數是一個int *,就語言規范而言,一個void *是不行的,更不用說一個指向int以外的完整型別的指標。現在,一個給定的 C 實作所提供的所有物件指標型別具有相同的大小和表示方法是相當普遍的,這樣,通過 cast 在它們之間進行轉換并不影響值的表示。 在這種情況下,你的轉換本身雖然不正確,但可能不會對
scanf造成實際問題。 (但是它可能,原因可能是模糊的或神秘的。 這就是 UB 的本質。)然而,事實是這樣的。
然而,事實是被指向的物件不是
int,也不是與int型別兼容的物件,所以如果scanf試圖通過指標訪問該物件,那么也會因為這個原因發生未定義行為。 這就違反了 "嚴格別名規則"。 與 cast 不同的是,這條規則在實踐中很可能會導致可觀察到的錯誤行為。
- 為什么會出現這種情況?
- 為什么
scanf()在被投到int*的時候可以讀到一個8位的型別,這可以通過a和b的直接printf驗證? 它不應該超限嗎?誰說它不會超限? 你的程式有未定義的行為。 這可以表現為做你所期望的事情,或者表現為出現這樣的情況。 在這個特定的案例中,我傾向于猜測,做正確事情的表象部分取決于你讀取變數的順序,以及(我推斷)你使用的是小位元組計算機,例如基于 Intel 的計算機。
- 反過來說,為什么
printf()不能列印被解讀為*(int*)v[0]的8位型別,而scanf可以讀入它們? 它們?你再次通過訪問
a和b來呼叫未定義的行為,就好像它們是ints,而事實上它們不是。 未定義行為不一定是一致的。 或者說,它可能與在C語言層面上沒有表現出來的結構和行為一致。 這不是語言欠你一個解釋的東西:這就是 "未定義 "的意思。
- 是否有一些編譯器時間的魔法來告訴 scanf/printf 資料型別是什么,因為格式指定器無疑是不充分的? 不夠?
格式字串是語言規范所要求的全部內容,它絕對足以告訴
scanf和printf應該期待什么。 這還不足以使它們能夠驗證引數型別實際上是它們被告知期望的東西,但是如果它們有這樣的能力,那么它們首先就不需要格式字串來告訴它們的型別。 提供符合格式字串的引數是程式員的責任,這并不繁瑣,因為程式員自己也提供了格式字串。 在scanf的情況下,提供有效的指標值也是程式員的責任,scanf可以在不違反嚴格別名規則的情況下使用。 該語言規定了當你正確操作時會發生什么;當你不正確操作時,它并沒有承諾任何特定的行為。轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/315427.html
標籤:
下一篇:面臨這個錯誤:TempCodeRunnerFile.c:3:38:注意:期望'int*'但引數是'int'型別voidprintArray(introw,int
