Is char Nice ? : C語言中char型別有問題且不會遞增的程式碼?
大家好,我是無能。
我很好奇,所以試了一下。
https://blog.httrack.com/blog/2014/05/30/c-corner-cases-and-funny-things
首先,我會嘗試在上述狀態下編譯並執行。
#include <stdio.h>
/** Return the next character within a \0-terminated string, or EOF. **/
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return buffer[*offs++]; /* here's the trap */
} else {
return EOF;
}
int main() {
const char *buffer = "Hello, World!";
size_t offs = 0;
printf("Reading characters from buffer:\n");
while (1) {
int ch = my_read_char(buffer, &offs);
if (ch == EOF) {
break;
}
printf("Character read: %c, offs: %zu\n", ch, offs);
}
return 0;
}
接下來,我會編譯並執行它。
gcc -o bugcode bugcode.c
./bugcode
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Charact^C
alleycat:[haturatu]:~/clang$
呀啊啊啊!!!
雖然很危險,但我還是讓它將日誌輸出到一個名為log的文件中,看看最初的執行情況。
話說回來,即使我急忙執行ps -ef | grep bugcode確認進程ID並將其kill掉,結果還是...
alleycat:[haturatu]:~/clang$ ls -la
合計 1256824
drwxr-xr-x 2 haturatu haturatu 4096 6月 14 23:38 .
drwx------ 39 haturatu haturatu 4096 6月 14 23:38 ..
~
-rw-r--r-- 1 haturatu haturatu 1286922240 6月 14 23:39 log
文件大小變得非常驚人,達到了約1.2GB...這已經是足以讓系統崩潰的危險程式碼了...無法用view查看,但可以用vim打開。
alleycat:[haturatu]:~/clang$ head log
Reading characters from buffer:
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
Character read: H, offs: 0
從一開始就沒有更新(遞增)。
我想在ChatGPT的幫助下理解這是怎麼回事。
EOF 與 \0 的區別
EOF(End Of File)是一個表示文件結束的特殊值,通常為 -1。另一方面,'\0' 是 C 語言字串的終止符,其值為 0。
感覺好像在這裡明白了什麼,但我的頭腦卻混亂了起來。
為了冷靜一下,我會在這裡詢問這段程式碼的執行流程。請不要介意它變成了my_read_char_with_cast。
流程詳情
初始狀態:
offs 為 0。
緩衝區沒有空字元終止符(沒有 '\0')。
循環開始:
呼叫 my_read_char_with_cast 函數。
buffer[*offs] 是 'H' (第一個字元),所以不是 '\0'。
返回 'H',offs 變為 1。
循環繼續:
下一次呼叫時,讀取 buffer[1] ('e')並返回。
offs 變為 2。
重複:
此過程繼續。
最終 offs 變為 11,讀取 buffer[11] ('!')並返回。
offs 變為 12。
無限循環:
buffer[12] 的值是未定義的(參照緩衝區外的記憶體區域),並且不是 '\0'。
由於 buffer[*offs] 始終不是 '\0',因此在未達到 EOF 的情況下發生無限循環。
反而變得更不明白了。實際測試結果是從一開始就發生了無限循環,所以offs根本沒有達到12。進行偵錯時:
Returning character: H
Character read: H, offs: 0
my_read_char called with offs: 0
Returning character: H
Character read: H, offs: 0
my_read_char called with offs: 0
Returning character: H
Character read: H, o^C
alleycat:[haturatu]:~/clang$
果然,offs仍然是0。
那麼,現在讓我們回到HTTrack的部落格。
這個函數對於 ASCII > 127 的值會返回負值。特別是對於 ASCII 255 (0xFF),它會返回 -1。這也是 EOF 的值。
原來如此?那麼為什麼會返回 -1 呢?我來問問AI。
char 型的特殊性
帶符號的8位元整數: C 語言的 char 型別通常被視為帶符號的8位元整數。這可以表示從 -128 到 127 的範圍。
環繞: 當 char 型別的值達到最大值(127)時,下一個遞增操作會環繞到最小值(-128)。這是由於二補數表示法,導致 127 遞增後會變成 -128。
關於 ASCII 255(0xFF)的考量
超出 ASCII 範圍: 在 ASCII 表中,值 255(0xFF)超出範圍,通常被視為控制字元。
與 EOF 的關係: 在 C 語言中,EOF 是表示文件結束的特殊值,通常被視為 -1。EOF 用於指示文件結束,但在這裡它被用作 ASCII 值 255(0xFF)的返回值。
函數行為的意義
這個函數對於 ASCII 255(0xFF)返回 -1 的原因如下:
超出範圍的值: ASCII 255 超出 char 型別的範圍,通常被視為無效值。
利用 EOF: 這個函數使用 EOF(-1)來指示緩衝區的結束。對於 ASCII 255 返回 -1,這是一種隱含的約定,表示 EOF。
這種設計使得函數能夠檢測緩衝區的結束並返回 EOF,從而作為一個信號,讓呼叫者停止讀取數據。然而,在這個函數的上下文中,如果需要對 ASCII 255 進行特定處理,或者希望正確處理 ASCII 255,則需要注意。
原來如此,char 型別本身就無法處理。這是因為 ASCII 127 和 ASCII 255 的差異,導致 EOF 作為 -1 返回,從而產生無限循環。
HTTrack 網站的修正程式碼如下。然而,我目前用 gcc 編譯了以下程式碼,但仍然發生了無限循環,所以看起來這也不行。
/** Return the next character within a \0-terminated string, or EOF. **/
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return (unsigned char) buffer[*offs++];
} else {
return EOF;
}
AI 顯示了這個。
int my_read_char(const char *buffer, size_t *offs) {
if (buffer[*offs] != '\0') {
return buffer[(*offs)++];
} else {
return EOF;
}
}
總之,即使要進行型別轉換,似乎也必須明確地將其作為 char 型別進行遞增。
我又問了關於這兩者的區別。
差異詳情
1. unsigned char へのキャストを含む関数
轉換的原因:
buffer 是 char 陣列,buffer[*offs] 返回 char 型別的值。
char 型別通常是帶符號的8位元整數,但這取決於實作(根據編譯器或平台,也可能是無符號的)。
如果字串中的值為負值,在沒有轉換的情況下,負值在擴展為 int 型別時可能會保留符號。
例如,如果 buffer[*offs] 的值是 -1,沒有轉換會返回 -1,但轉換後會返回 255(0xFF)。
2. キャストなしの関数
帶符號字元的處理:
如果 buffer[*offs] 可能帶有負值,沒有轉換會直接返回負值。
這在某些情況下可能會導致意外行為。
那麼,結果如何呢?
應該使用哪一個?
包含轉換的函數:
在將字元數據作為二進位處理或希望將其作為無符號值處理時,通常會轉換為 unsigned char。
這對於處理非 ASCII 字元(例如 UTF-8 編碼的數據)尤其重要。
沒有轉換的函數:
如果只處理 ASCII 字元,或者有意將其作為帶符號值處理,則不進行轉換可能沒有問題。
原來這是一個相當特殊的例子啊。
對於後者不進行型別轉換的情況,透過明確地將其表示為 buffer[(*offs)++] 來作為 char 型別處理,這是否意味著它會作為 char 型別的預設無符號處理,從而正確地進行 char 型別的遞增呢?
字元編碼的差異確實很難視覺化,生活中有很多這樣的陷阱呢...。哎呀,真有趣!