看板 C_and_CPP 關於我們 聯絡資訊
在 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
qsort : 我猜,該檔案被鎖住了,所以git無法access,才會報錯07/12 15:52
qsort : 建議用.gitignore把這類temp檔ignore掉,不要上到git。07/12 15:53
gitignore: 有人叫我?07/13 04:41
-- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 122.116.185.23 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1507613289.A.22D.html
jerryh001: C語言的特色就是更快,還要更快 額外的動作都浪費時間 10/10 13:38
AstralBrain: 那你希望int x = *VACANT; 的x的值是多少 10/10 13:45
AstralBrain: 未初始化的垃圾值? 這樣跑到後面會出現更多問題 10/10 13:45
AstralBrain: 還不如在一開始直接死 10/10 13:45
就跟 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 的做法
AstralBrain: 如果等號左邊是複雜的c++ class那更無解了 10/10 13:47
C++ 太複雜惹
AstralBrain: 所以其實你要的不是VACANT, 而是對void做各種運算..? 10/10 13:53
AstralBrain: 那 int *ptr = (int*)VACANT; 會發生什麼事 10/10 13:55
這個應該就真的比較複雜了 假定編譯器頭文件是這樣實作的 #define VACANT ((void*)(0xFFFFFFFFFFFFFFF)) 也就是要從 CPU 設計變更下手 只要對存取最後一個保留地址當作沒反應的指令 就像 NOP 一樣 這樣也許可行吧(?
AstralBrain: 其實我想問的是你允不允許VACANT轉型成其他指標 XD 10/10 14:06
AstralBrain: 如果允許的話就回到我一開始的問題 10/10 14:06
二樓的那個問題在於「把 void 型態賦值到 int」 如果改成 int x = *(uintptr_t*)VACANT; 的話... 答案大概就是... x = 0xFFFFFFFF 吧(從 64bit 變成 int: 32bit 截斷)
AstralBrain: *(int*)VACANT 要segfault還是要NOP 10/10 14:07
AstralBrain: segfault => 跟null有87%像, nop => 跑到後面更慘 10/10 14:08
*(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 的遞補(?
AstralBrain: 實作好處理 問題是要怎麼限制vacant只能write-only 10/10 14:23
好問題 @@ 如果是 int *ptr = VACANT; printf("%d\n", *ptr); 的話呢... 應該還是 segfault 跟 NULL 一樣 我覺得應該是這樣 畢竟 VACANT 的地址不在程序的可以讀的權限裡 但是所有的程序對那個地址都有寫入的權限 跟 /dev/null 一樣 只要讀就是直接拿到一個 EOF
AstralBrain: 現在你要的功能應該可以用c++自己做一個出來 10/10 14:25
AstralBrain: 在smart pointer外面再包一層之類的 10/10 14:26
AstralBrain: 可以先自己試用看看 XD 10/10 14:26
CoNsTaR: 就算這樣比較好編譯器也做不到啊 10/10 14:27
CoNsTaR: 因為很多情況下編譯器沒辦法知道一個變數(當然也包含指 10/10 14:27
CoNsTaR: 標變數)的值 10/10 14:27
CoNsTaR: 例如假設有個不可判定的問題,它有兩種可能的答案(例如是 10/10 14:27
CoNsTaR: 、否) 10/10 14:27
CoNsTaR: 寫一個試圖解決這個問題的函式,如果答案是是,那就將指 10/10 14:27
CoNsTaR: 標 assign 為 NULL,否則 assign 為其他值 10/10 14:27
CoNsTaR: 那如果編譯器要知道這個指標的值為何,就必須要先知道這 10/10 14:27
CoNsTaR: 個問題的答案 10/10 14:27
CoNsTaR: 所以很明顯這樣的構想是不能成立的 10/10 14:27
沒錯 正因為如此 所以才用 preprocessor macro 指定一個機器相依的數值 編譯器不需要知道 void* 型態的變數是不是裝 VACANT 因為 VACANT 就是一個定值 當語言被編譯成執行檔的時候 會變成 movl $10, 0x8 當機器要執行這行指令的時候 就會直接把 10th register 的值直接丟掉 讀取的時候也會 因為程序沒有這段地址的讀取權限產生 segfault
CoNsTaR: 或者說,那個問題的答案是一個整數,然後你把答案轉型 as 10/10 14:32
CoNsTaR: sign 給指標,那編譯器就得知道它的答案是否為零 10/10 14:32
LPH66: 你在講的不就是在編譯時期擋下來嗎? 10/10 15:50
LPH66: 那你又提執行時期的數值做什麼... 10/10 15:50
ㄛㄛ我忘了改 XD 那是最早的討論 所以後來我們改變了一點實作的方向 現在覺得用軟解不太好 用硬體去解應該好得多
LPH66: 然後就是因為編譯時期擋不下來 (理由如上述) 才會變成 UB 10/10 15:51
LPH66: 硬解那就跟 C 語言無關啦, 其他人又不用你的特別硬體 10/10 15:56
沒錯 一開始覺得靠 compiler 就行了 但是好像問題越來越複雜 如果把這個問題交給 CPU 去處理會簡單非常多
LPH66: 然後 (雖然離原題離版題都很遠) 你或許可以看看 MIPS 10/10 16:02
LPH66: 它的 $0 這個 register 是常數零, 讀它得零, 寫它是 NOP 10/10 16:02
LPH66: 雖然這是 register 不是位址但大概跟你想要的東西沾了個邊 10/10 16:03
恩恩 $0 是 hard-wired 的 如果要像我說的設計的話就是要寫一個小組合電路去控制指令佇列 有點不太一樣
Lipraxde: 不是應該先設計指令集在用硬體實作嗎? 10/10 19:15
sppmg: C不是一直被稱作中階語言嗎 ^_^ 10/10 19:33
C 語言素有最低階的高階語言之稱 www
chchwy: Objective-C提供了喔 10/10 19:49
Bencrie: x86 realmode 就不會噴 segfault 啦 10/10 20:01
Bencrie: 但是改 IVT 會發生什麼事就不知道惹 10/10 20:02
lc85301: 好了大家別吵了都來寫Rust吧(誤 10/10 21:17
噗要
PkmX: 好奇你的VACANT要怎麼減少sentinel value和減少判斷 10/10 22:10
我想要設計一個 circular queue 如果先填入 sentinel value (SV) 的話 那接下來的使用就要去判斷 如果是 SV 的話就要略過 這樣效率不好 問題卡在 queue 一剛開始運作還沒填滿的時候 就算用額外的變數儲存目前佇列儲存了幾個元素 使用的時候還是要去判斷是不是已經滿了 而在我目前的這個情況 剛好不適合宣告兩個變數分別指向頭跟尾 所以只有一個變數的話 SV 就顯得重要
AstralBrain: 仔細一想反正你只是要一塊write-only的垃圾位址 10/10 22:22
AstralBrain: 那 void* vacant = new char[4096]; 就好啦 XD 10/10 22:22
AstralBrain: 是不是真的nop也不是很重要 10/10 22:25
沒錯我後來就是用這個方法 workaround
CoNsTaR: 其實 Idris、Agda 的編譯器可以做到你想的東西,只是它 10/10 23:35
CoNsTaR: 們是和 C 非常不同的語言 10/10 23:35
CoNsTaR: 我是覺得 C 應該無法(也不需要)做到這樣的事情啦 10/10 23:35
硬體支援 C 語言就能跟上惹(誤
azureblaze: 沒有痛覺神經就不怕被刀砍了 10/11 00:19
azureblaze: 程式寫錯會crash是好事 10/11 00:21
azureblaze: debug寫入資料遺失聽起來就超好玩 10/11 00:26
提早 crash 是好了 至少知道程序是錯的 沒掛掉有時候不代表是正確的 XD
james732: 其實有點好奇其他語言(如rust)怎麼解決這問題 10/11 00:59
PkmX: 痾你還是沒有解釋如何用VACANT避免circular buffer的SV和 10/11 01:26
PkmX: 判斷啊 有了VACANT不用判斷就可以知道buffer滿了? 10/11 01:26
是的 因為我的 circular queue 裝的是 pointers to type T 所以如果能 pre-filled with VACANT which is of type void* 那麼如此一來我每次要使用的時候就不怕 dereference 到 sentinel 了 就算遇到 sentinel 我也能直接 deref. 而不用怕寫至奇怪的地址 因為我處理的這個情況比較特別 要 push 一整圈完之後才開始 pop 如果能等價於在 push 的同時也 pop 前一個那麼 code 會比較 concise 這個時候 VACANT 就很有用 可以讓我只需要一個 local variable
PkmX: 你一開始裡面都塞VACANT那你push的東西到底寫到哪裡去了? 10/11 01:53
前幾次要取的時候會有很多個 VACANT 等到操作越來越多次後 VACANT 就會漸漸被我塞入的值取代直到完全消失為止 就不必再之後的每次迴圈裡產生 conditional branch 來判斷 SV 概念上大概是 int index = -1; TYPE *queue[64] = {VACANT, ...}; // 64 times while ( /* CONDITIONS */ ) { index = (index + 1) % 64; // range 0~63 *(queue[index]) = ...; // write once /* 問題在這 如果沒有 VACANT 事情就會比較麻煩 */ queue[index] = ...; }
CoNsTaR: 可是 C 預設是不做 prefill 的啊,這不符合 C 的精神10/11 02:10
CoNsTaR: 我覺得你這樣又 prefill 拖效能,遇到錯誤又讓他蒙混過去10/11 02:10
CoNsTaR: 當作沒發生,而且又不直覺,真心覺得不是什麼好辦法10/11 02:10
CoNsTaR: 現在其他語言處理這種問題不拖效能而且又能在編譯時期處10/11 02:10
CoNsTaR: 理完的通常都是用 depnedent types 吧…10/11 02:10
這是個好問題 我要操作這個 queue 超過 10兆 次以上 但是 prefill 這個 queue 也不過 64 個 elements 幾乎是微乎其微 兩權相害取其輕 XD
CoNsTaR: 像 rust 也有用的 linear types 可以知道哪些東西存取過10/11 02:15
CoNsTaR: 了,哪些還沒,和存取次數,而且也是編譯時期就檢查完,10/11 02:15
CoNsTaR: 也是一種方法10/11 02:15
PkmX: 你那樣寫的意思是前面幾個iteration *(queue[index]) 因為 10/11 03:00
PkmX: pointer 是 VACANT 所以值寫進去被 discard 也是合理的? 10/11 03:00
Hazukashiine: 嗯嗯 我希望是這樣沒錯 10/11 03:03
PkmX: 這樣的話 最直覺的方式還是就分配一個垃圾位置當初始值就好 10/11 03:22
PkmX: 或是既然你知道最後SV會不見 就分成兩個版本 前面的需要檢查 10/11 03:23
PkmX: 而後面的phase已知SV不存在 就不需要檢查 當然比較進階的 10/11 03:23
PkmX: type system可以幫你紀錄你的資料結構裡面是否還存在SV 10/11 03:24
PkmX: (e.g. phantom type) 不過這個已經扯遠了 10/11 03:25
PkmX: 當然程式語言或是硬體是否要支援這種blackhole的位置我想 10/11 03:26
PkmX: 實作上都是沒有問題的 只是有沒有必要為了這個例子而去複雜 10/11 03:26
PkmX: 化語言的spec或是硬體的ISA罷了 10/11 03:26
PkmX: 另外多了一個branch在整個loop中+有分支預測的CPU執行下所 10/11 03:28
PkmX: 造成的效能影響多寡也是一個要探討的問題 10/11 03:28
kaneson: 不會叫的bug才是最難找的 10/11 13:00
dou0228: 同意樓上,忽略是一個最糟糕的寫法,不應該這樣做 10/11 17:10
samuelcdf: 看起來好像只是把programmer的責任丟給compiler一樣 10/12 15:36
samuelcdf: 就跟很多有自動記憶體配置回收的語言一樣, 寫的不好, 10/12 15:38
samuelcdf: 只是延後整個軟體掛掉的時間一樣. 而且更難除錯. 10/12 15:38
uranusjr: 只要忽略的邏輯清楚行為直觀就不糟糕, 斷言「不應該」是 10/14 02:12
uranusjr: 不太好...其實也有很多語言這麼做, 尤其 Smalltalk 派 10/14 02:12
uranusjr: 其實原 po 的發想完全很合理, 也是已經被充分討論的議題 10/14 02:13
uranusjr: 只是和 C 類語言發展的方向不同, 所以感覺在這裡沒溫暖 10/14 02:14
uranusjr: 結論是快轉換陣營吧 C-like languages 不是一切 (欸 10/14 02:14
※ 編輯: Hazukashiine (122.116.185.23), 10/14/2017 11:40:43
Killercat: 這東西我在Obj-C被婊了無數次,這不是一個好方法... 10/14 21:55
Killercat: Obj-C你對任何nil(相當於nullptr)的操作都會無聲的過去 10/14 21:56
Killercat: 應該說,這對大多數C/C++ user來講 是很不習慣的事情 10/14 21:56
Killercat: 另外除錯困難等級裡面 pre-compiler < compiler < run 10/14 21:58
Killercat: time <<<<<<< silently pass 這剛好屬於最糟的一種 10/14 21:58
LiloHuang: 推 Killercat 的見解,沒有 segfault 無聲無息很糟糕.. 10/15 00:41
steve1012: 不會報錯的錯是最慘的 幾乎沒好處 10/15 06:47
y3k: 這個設計應該有某部分人會需要 感覺也可行 但是對正常人來說 10/15 11:25
y3k: B>Z 原因很簡單就是上面說的不會叫的bug最難找 這種設計下去 10/15 11:26
y3k: 往往只是逼Programer更頻繁的check null或你說的虛無... 10/15 11:27
y3k: 而且你要知道正常的C/C++ Code規模XD 10/15 11:28
y3k: 所以如果要做 應該是可以做 但是絕對不會預設為啟用 這很糟 10/15 11:28