[閒聊] 為什麼 C 語言不提供指向虛無的指標

看板C_and_CPP (C/C++)作者 (みなさん、こんにちは)時間7年前 (2017/10/10 13:28), 7年前編輯推噓24(24073)
留言97則, 21人參與, 7年前最新討論串1/1
在 C 語言裡面對 NULL 取值是非法的 通常伴隨的是發生 segmentation fault 而這通常也跟 CPU 的指令集實作有關 因為編譯器會將對 NULL 取值的程式碼 假定 NULL 就是常數數值 0 的情況下 編譯成類似 movl $10, 0x0 等的指令 因此 CPU 在處理相關指令的時候 豎立 flag 觸發 OS 處理是可以預期的 但是既然 C 語言被定調成高階語言 為什麼不提供一些抽象一些的語意 像是對「虛無指針」取值是代表忽略的意思 比如說 VACANT 代表「虛無指針」 則 void *arr[] = {VACANT, VACANT}; *arr[1] = (uintptr_t)12345; 代表什麼事(包含副作用)都不會發生 感覺對空虛取值不發生作用非常實用 就像指令集幾乎都會有 NOP 一樣 表面上看起來沒什麼用 但是卻能在不少 corner cases 發生作用 NOP 可以去解決 hazard 的問題 VACANT 可以解決不少指針初始化的問題 可以減少程式設計所需的 sentinel value 還在特定的時候能讓程式減少無謂的判斷式 基本上我還沒想到什麼負面的影響 我的問題是為什麼不設計類似的指針? 類似 /dev/null 的概念 看起來沒什麼用 但是卻大大有用 XD -- 作者 sr29 (owo) 看板 Linux 標題 [問題] git add 失敗 時間 Wed Jul 12 15:31:13 2017

07/12 15:52,
我猜,該檔案被鎖住了,所以git無法access,才會報錯
07/12 15:52

07/12 15:53,
建議用.gitignore把這類temp檔ignore掉,不要上到git。
07/12 15:53

07/13 04:41,
有人叫我?
07/13 04:41
-- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 122.116.185.23 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1507613289.A.22D.html

10/10 13:38, 7年前 , 1F
C語言的特色就是更快,還要更快 額外的動作都浪費時間
10/10 13:38, 1F

10/10 13:45, 7年前 , 2F
那你希望int x = *VACANT; 的x的值是多少
10/10 13:45, 2F

10/10 13:45, 7年前 , 3F
未初始化的垃圾值? 這樣跑到後面會出現更多問題
10/10 13:45, 3F

10/10 13:45, 7年前 , 4F
還不如在一開始直接死
10/10 13:45, 4F
就跟 NULL 一樣 型態都是 void* 只是一種特殊指標 1. void *ptr = VACANT; *ptr = dont_care; // 沒事發生 或是說 2. void val = "i don't care"; // 一樣沒事 也就是說對 void 賦值是無反應的 www 但是如果是 3. void *ptr = NULL; *ptr = "segfault" // 錯誤發生 這個就是按照 lagacy 的做法

10/10 13:47, 7年前 , 5F
如果等號左邊是複雜的c++ class那更無解了
10/10 13:47, 5F
C++ 太複雜惹

10/10 13:53, 7年前 , 6F
所以其實你要的不是VACANT, 而是對void做各種運算..?
10/10 13:53, 6F

10/10 13:55, 7年前 , 7F
那 int *ptr = (int*)VACANT; 會發生什麼事
10/10 13:55, 7F
這個應該就真的比較複雜了 假定編譯器頭文件是這樣實作的 #define VACANT ((void*)(0xFFFFFFFFFFFFFFF)) 也就是要從 CPU 設計變更下手 只要對存取最後一個保留地址當作沒反應的指令 就像 NOP 一樣 這樣也許可行吧(?

10/10 14:06, 7年前 , 8F
其實我想問的是你允不允許VACANT轉型成其他指標 XD
10/10 14:06, 8F

10/10 14:06, 7年前 , 9F
如果允許的話就回到我一開始的問題
10/10 14:06, 9F
二樓的那個問題在於「把 void 型態賦值到 int」 如果改成 int x = *(uintptr_t*)VACANT; 的話... 答案大概就是... x = 0xFFFFFFFF 吧(從 64bit 變成 int: 32bit 截斷)

10/10 14:07, 7年前 , 10F
*(int*)VACANT 要segfault還是要NOP
10/10 14:07, 10F

10/10 14:08, 7年前 , 11F
segfault => 跟null有87%像, nop => 跑到後面更慘
10/10 14:08, 11F
*(int*)VACANT = (int)5487; 的話應該比較簡單 畢竟 VACANT 代表的就是最後的保留地址的數值,那轉型再取值應該就還是沒反應吧 應該是這麼說 VACANT 的數值也是機器依賴的 編譯器要端看 CPU 是怎麼實作的 以假設對 CPU 來說地址第 8~15 位元組用作忽略地址的話 那麼 #define VACANT ((void*)(0x8)) 才是正確的 不過這在 CPU 解碼前期就要多一個小電路 讓該送進來指令變成氣泡或是直接讓下一個指令直接遞補上 這樣其實也不會太難 只要 63 個 OR gates 和 1~2 個 NOT gate 去當 mux 的 select 看是是不是要觸發 instruction queue 的遞補(?

10/10 14:23, 7年前 , 12F
實作好處理 問題是要怎麼限制vacant只能write-only
10/10 14:23, 12F
好問題 @@ 如果是 int *ptr = VACANT; printf("%d\n", *ptr); 的話呢... 應該還是 segfault 跟 NULL 一樣 我覺得應該是這樣 畢竟 VACANT 的地址不在程序的可以讀的權限裡 但是所有的程序對那個地址都有寫入的權限 跟 /dev/null 一樣 只要讀就是直接拿到一個 EOF

10/10 14:25, 7年前 , 13F
現在你要的功能應該可以用c++自己做一個出來
10/10 14:25, 13F

10/10 14:26, 7年前 , 14F
在smart pointer外面再包一層之類的
10/10 14:26, 14F

10/10 14:26, 7年前 , 15F
可以先自己試用看看 XD
10/10 14:26, 15F

10/10 14:27, 7年前 , 16F
就算這樣比較好編譯器也做不到啊
10/10 14:27, 16F

10/10 14:27, 7年前 , 17F
因為很多情況下編譯器沒辦法知道一個變數(當然也包含指
10/10 14:27, 17F

10/10 14:27, 7年前 , 18F
標變數)的值
10/10 14:27, 18F

10/10 14:27, 7年前 , 19F
例如假設有個不可判定的問題,它有兩種可能的答案(例如是
10/10 14:27, 19F

10/10 14:27, 7年前 , 20F
、否)
10/10 14:27, 20F

10/10 14:27, 7年前 , 21F
寫一個試圖解決這個問題的函式,如果答案是是,那就將指
10/10 14:27, 21F

10/10 14:27, 7年前 , 22F
標 assign 為 NULL,否則 assign 為其他值
10/10 14:27, 22F

10/10 14:27, 7年前 , 23F
那如果編譯器要知道這個指標的值為何,就必須要先知道這
10/10 14:27, 23F

10/10 14:27, 7年前 , 24F
個問題的答案
10/10 14:27, 24F

10/10 14:27, 7年前 , 25F
所以很明顯這樣的構想是不能成立的
10/10 14:27, 25F
沒錯 正因為如此 所以才用 preprocessor macro 指定一個機器相依的數值 編譯器不需要知道 void* 型態的變數是不是裝 VACANT 因為 VACANT 就是一個定值 當語言被編譯成執行檔的時候 會變成 movl $10, 0x8 當機器要執行這行指令的時候 就會直接把 10th register 的值直接丟掉 讀取的時候也會 因為程序沒有這段地址的讀取權限產生 segfault

10/10 14:32, 7年前 , 26F
或者說,那個問題的答案是一個整數,然後你把答案轉型 as
10/10 14:32, 26F

10/10 14:32, 7年前 , 27F
sign 給指標,那編譯器就得知道它的答案是否為零
10/10 14:32, 27F

10/10 15:50, 7年前 , 28F
你在講的不就是在編譯時期擋下來嗎?
10/10 15:50, 28F
還有 31 則推文
還有 11 段內文
這是個好問題 我要操作這個 queue 超過 10兆 次以上 但是 prefill 這個 queue 也不過 64 個 elements 幾乎是微乎其微 兩權相害取其輕 XD

10/11 02:15, 7年前 , 60F
像 rust 也有用的 linear types 可以知道哪些東西存取過
10/11 02:15, 60F

10/11 02:15, 7年前 , 61F
了,哪些還沒,和存取次數,而且也是編譯時期就檢查完,
10/11 02:15, 61F

10/11 02:15, 7年前 , 62F
也是一種方法
10/11 02:15, 62F

10/11 03:00, 7年前 , 63F
你那樣寫的意思是前面幾個iteration *(queue[index]) 因為
10/11 03:00, 63F

10/11 03:00, 7年前 , 64F
pointer 是 VACANT 所以值寫進去被 discard 也是合理的?
10/11 03:00, 64F

10/11 03:03, 7年前 , 65F
嗯嗯 我希望是這樣沒錯
10/11 03:03, 65F

10/11 03:22, 7年前 , 66F
這樣的話 最直覺的方式還是就分配一個垃圾位置當初始值就好
10/11 03:22, 66F

10/11 03:23, 7年前 , 67F
或是既然你知道最後SV會不見 就分成兩個版本 前面的需要檢查
10/11 03:23, 67F

10/11 03:23, 7年前 , 68F
而後面的phase已知SV不存在 就不需要檢查 當然比較進階的
10/11 03:23, 68F

10/11 03:24, 7年前 , 69F
type system可以幫你紀錄你的資料結構裡面是否還存在SV
10/11 03:24, 69F

10/11 03:25, 7年前 , 70F
(e.g. phantom type) 不過這個已經扯遠了
10/11 03:25, 70F

10/11 03:26, 7年前 , 71F
當然程式語言或是硬體是否要支援這種blackhole的位置我想
10/11 03:26, 71F

10/11 03:26, 7年前 , 72F
實作上都是沒有問題的 只是有沒有必要為了這個例子而去複雜
10/11 03:26, 72F

10/11 03:26, 7年前 , 73F
化語言的spec或是硬體的ISA罷了
10/11 03:26, 73F

10/11 03:28, 7年前 , 74F
另外多了一個branch在整個loop中+有分支預測的CPU執行下所
10/11 03:28, 74F

10/11 03:28, 7年前 , 75F
造成的效能影響多寡也是一個要探討的問題
10/11 03:28, 75F

10/11 13:00, 7年前 , 76F
不會叫的bug才是最難找的
10/11 13:00, 76F

10/11 17:10, 7年前 , 77F
同意樓上,忽略是一個最糟糕的寫法,不應該這樣做
10/11 17:10, 77F

10/12 15:36, 7年前 , 78F
看起來好像只是把programmer的責任丟給compiler一樣
10/12 15:36, 78F

10/12 15:38, 7年前 , 79F
就跟很多有自動記憶體配置回收的語言一樣, 寫的不好,
10/12 15:38, 79F

10/12 15:38, 7年前 , 80F
只是延後整個軟體掛掉的時間一樣. 而且更難除錯.
10/12 15:38, 80F

10/14 02:12, 7年前 , 81F
只要忽略的邏輯清楚行為直觀就不糟糕, 斷言「不應該」是
10/14 02:12, 81F

10/14 02:12, 7年前 , 82F
不太好...其實也有很多語言這麼做, 尤其 Smalltalk 派
10/14 02:12, 82F

10/14 02:13, 7年前 , 83F
其實原 po 的發想完全很合理, 也是已經被充分討論的議題
10/14 02:13, 83F

10/14 02:14, 7年前 , 84F
只是和 C 類語言發展的方向不同, 所以感覺在這裡沒溫暖
10/14 02:14, 84F

10/14 02:14, 7年前 , 85F
結論是快轉換陣營吧 C-like languages 不是一切 (欸
10/14 02:14, 85F
※ 編輯: Hazukashiine (122.116.185.23), 10/14/2017 11:40:43

10/14 21:55, 7年前 , 86F
這東西我在Obj-C被婊了無數次,這不是一個好方法...
10/14 21:55, 86F

10/14 21:56, 7年前 , 87F
Obj-C你對任何nil(相當於nullptr)的操作都會無聲的過去
10/14 21:56, 87F

10/14 21:56, 7年前 , 88F
應該說,這對大多數C/C++ user來講 是很不習慣的事情
10/14 21:56, 88F

10/14 21:58, 7年前 , 89F
另外除錯困難等級裡面 pre-compiler < compiler < run
10/14 21:58, 89F

10/14 21:58, 7年前 , 90F
time <<<<<<< silently pass 這剛好屬於最糟的一種
10/14 21:58, 90F

10/15 00:41, 7年前 , 91F
推 Killercat 的見解,沒有 segfault 無聲無息很糟糕..
10/15 00:41, 91F

10/15 06:47, 7年前 , 92F
不會報錯的錯是最慘的 幾乎沒好處
10/15 06:47, 92F

10/15 11:25, 7年前 , 93F
這個設計應該有某部分人會需要 感覺也可行 但是對正常人來說
10/15 11:25, 93F

10/15 11:26, 7年前 , 94F
B>Z 原因很簡單就是上面說的不會叫的bug最難找 這種設計下去
10/15 11:26, 94F

10/15 11:27, 7年前 , 95F
往往只是逼Programer更頻繁的check null或你說的虛無...
10/15 11:27, 95F

10/15 11:28, 7年前 , 96F
而且你要知道正常的C/C++ Code規模XD
10/15 11:28, 96F

10/15 11:28, 7年前 , 97F
所以如果要做 應該是可以做 但是絕對不會預設為啟用 這很糟
10/15 11:28, 97F
文章代碼(AID): #1Pt5ff8j (C_and_CPP)
文章代碼(AID): #1Pt5ff8j (C_and_CPP)