我曾在某論壇裡,為解釋檔案讀寫時, binary mode 與 txt mode 之差異,
所以寫了份 demo code 與簡要說明。
但由於該論壇必須註冊才可看到內容,後續在他處遇到有類似問題的人,
反而讓我感到不便,我不願因要回答一個問題答,便要求別人註冊一個論壇帳號,
於是將論壇該文重整過來,放上。若有該論壇之帳號,
倒是可直接先看原文與相關討論串。
以下之敘述與原文有些出入,但模式大致相同,只加強了一點 binary mode 之說明;
程式碼只小修一點地方,從 .cpp 改成 .c (變 C compiler,不用 C++ compiler)。
text mode 和 binary mode 差異我就不說了,直接用案例來說明。假設我有一個 data.txt 文字檔,用記事本 (notepad.exe) 開啟,內容大致如下所示看到的東西如下
接著我們用專門看 binary code 的軟體 - Ultra Edit / PSPad (free)去看 data.txt,一開始並沒太大變化,只是環境變了而已。
接下來是重點, PSPad 中,在 Menu 那裡按「檢視」-> HEX 編輯模式;
若為 UltraEditor,可直接按快捷鍵 Ctrl + H 做為切換。
接下來看到的便是一堆莫名的數字。左半部代表的是該文字檔裡所儲存的 16 進位碼,
右半段所顯示的為該文字檔被 PSPad 「 "畫" 」出來的文字,
別忘了,電腦裡面其實只有存 0/1 而已,至於「顯示」、「畫」出來的,
全部都是經過轉換而已,像 PSPad / Notepad.exe ,甚至一般寫的 console 程式,
也都是根據字元編碼 (最常見是 ASCII / ANSI) 方式,把那個「字」畫出來。
注意到上面反白處,一開始的 "5468",去查 ASCII Table ,
0x54 代表的是 'T' / 0x68 代表 'h';
(如果您從 ASCII Table 查到 'T" 是 84,代表你看到的是 10 進位,不是 16 進位)。
但一些本身就用 binary mode 寫進去的檔案,舉例而言,像是 mp3,
這時硬要用 notepad.exe ,以文本模式開啟本身就是一堆亂碼,
原因是裡面所存的數字,沒辦法被正確翻譯成某些文字,也就沒辦法被正確畫出,
而用 PSPad/UltraEditor 開啟時,便長得像這樣
這次裡面的數字是「完全看不懂」的,只有一開始的 0x49 0x44 0x33 去找
ASCII 有意義,代表 ID3(因 mp3 前面這 3 bytes 就固定是 ID3),
其它的 hex value,沒去翻過 mp3 format ,就看看就算吧。
再多看一例,rar 壓縮後再以 pspad 開啟,發現前面 3 bytes 固定為
0x52 0x61 0x72,固定為 rar,其他的都是亂碼,要去查 rar format 才知道意義。
鑑於這種現象,於是寫了一份 C code,欲仿 pspad / Ultra 去觀查
binary - txt mode 之文字與編碼,程式碼如下。
/*******************************************************************/
/* */
/* filename : Ultra.c */
/* author : edison.shih/edisonx */
/* compiler : Visual C++ 2008 */
/* date : 2009.03.07 */
/* */
/*******************************************************************/
#include <stdio.h>
#include <conio.h>
// filesize must < 2GB
unsigned long GetFileLength(const char* filename)
{
unsigned long len = 0;
FILE *fp=fopen(filename, "rb");
if(fp==NULL){
printf("error!!\n");
return 0;
}
fseek ( fp, 0L, SEEK_END );
len = ftell (fp);
fclose(fp);
return len;
}
void ShowBinary(const char* filename)
{
unsigned long i, j;
unsigned file_size;
unsigned char buffer[17]={0};
unsigned char ch;
unsigned long counter=0;
FILE *fp=fopen(filename, "rb");
if(fp==NULL){
printf("error!!\n");
return;
}
// start show
file_size = GetFileLength(filename);
// title
printf("%8s|", " ");
for(i=0x00; i<=0x0F; i++) printf("%02X ", i);
printf("|");
for(i=0x00; i<=0x0F; i++) printf("%1X", i);
printf("\n");
// --- sp ---
for(i=0; i<8; i++) putchar('-');
putchar('|');
for(i=0x00; i<=0x0f; i++) printf("---");
putchar('|');
for(i=0x00; i<=0x0f; i++) putchar('-');
// ---- show the context
for(i=0; i<file_size; i++){
if(i%0x10==0) printf("\n%08X|",i);
ch = (unsigned char)fgetc(fp), printf("%02X ", ch);
// 將可能換行、tab 之文字用. 去取代
if(ch=='\r' || ch=='\n' || ch=='\t') buffer[i%0x10] = '.';
else buffer[i%0x10] = ch;
if(i%0x10==0x0f) {
printf("|%s", buffer);
getch(); // 加上去為16筆筆慢慢看, 隨便按一鍵繼續
}
}
printf("\n");
printf("%s filelength:%lu", filename, file_size);
// end show
fclose(fp);
}
int main()
{
ShowBinary("data.txt");
return 0;
}
在執行時,一開始「可能」會顯示不正常,長這樣
那是因為 console 沒調好,在抬頭處按一下右鍵,選「內容」
切到版面設定,螢幕緩衝區大小寬度設80,視窗大小寬度也設 80。
上面前二個數值,螢幕緩衝區大小設定為 80,30。代表寬可以輸入 80 個字元,而整個 Console 可以有 30 行的暫存;
視窗大小即為一個 Console 畫面,寬度一次可看 80 個字元,高度一次可看 25 個字元。
若螢幕緩衝區大小大於視窗大小的時候,便產生了,一次不能看到所有的字元,故會產生捲軸出來,
這在遊戲設計的時候必須避免,也可以實際自己調過觀查後再做設定,好了之後按確定。
出現對話框,按「儲存內容,讓相同的標題視窗使用」。
妙的是,回到 console 裡雖然視窗寬度有變寬,還是一樣顯示不正常。
關掉它,重新再執行一次執行檔,這次正常多了。
由於在碼程式裡有加非標準函式 getch,所以每次讀 16 bytes ,都會停一下,
等待按任意鍵 (當初設計是方便觀查),多按幾次後執行結果如下。
console 版,簡單 PSPad / UltraEditor 功能已完成。
筆者自白,雖然標題很聳動,其實實作不到裡面的 1% 功能,
但希望藉由這種模式,
一方面能釐清 txt / binary 之讀寫模式,一方面也希望引起一些興趣。
這份 code 只到這裡,沒繼續開發下去原因在於,
UltraEditor / PSPad 類似軟體較適合以視窗方式開發,
且真正麻煩地方在於 語法分析、高亮、筆對、使用者自定義巨集 等,
要做得好,一個人必須花不少時間。