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 процесса и убил его...
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.2 ГБ... Это уже опасный код, способный привести к краху системы... Я не смог просмотреть его с помощью 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? Спрошу у ИИ.
char型の特殊性
符号付き8ビット整数: C言語のchar型は、通常、符号付きの8ビット整数として扱われます。これは、-128から127までの範囲を表すことができます。
ワープアラウンド: char型の値が最大値(127)に達すると、次のインクリメント操作で最小値(-128)にワープアラウンドします。これは、2の補数表現によるもので、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;
}
ИИ предложил это.
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 по умолчанию обрабатывается как беззнаковый?
Различия в кодировках символов довольно трудно визуализировать, и существует множество ловушек, с которыми мы сталкиваемся в повседневной жизни... Ох, это было интересно!