首先兩者讀取所有文件的方法都是采用迭代的方式,首先用函數A的返回值判斷目錄下是否有文件,然后返回值合法則在循環中用函數B直到函數B的返回值不合法為止。最后用函數C釋放資源。
1、打開目錄
#include <sys/types.h> #include <dirent.h> DIR *opendir(const char *name);
先看Linux的,返回的是DIR*,因此出錯時返回NULL(0)。而這里不用關心DIR結構具體定義,只需要知道是對它進行操作(注意:DIR不是保存文件信息的結構)
而Windows的方法很多,包括MFC的CFileFind類、WINAPI的WIN32_FIND_DATA和C運行庫的_finddata_t,這里選取最后一種
intptr_t _findfirst( const char *filespec, struct _finddata_t *fileinfo );
返回類型是intptr_t,這是用來表示兩個指針(地址)之間距離的類型(比如對指針類型p1,p2,用intptr_t來接收p1-p2的返回值),比如指針在32位系統上是4個字節,而在64位系統上是8個字節,通過#ifdef宏來實現跨平台的類型。
這里返回值其實是一個標識當前目錄下所有文件的HANDLE而不是實際指針類型,所以出錯時返回-1而不是0(NULL)。
再看輸入參數,第1個參數是filespec(而不是filename),很容易用錯,因為它不是像Linux的opendir一樣簡單地接收目錄名,而是接收一個特定格式。比如C:\*.*就代表搜索C盤下所有類型文件,而C:\*.txt則代表搜索C盤下所有txt文件。第2個參數是struct _finddata_t是實際存儲文件信息的結構。
2、遍歷文件
每個文件都有一個具體的結構來描述它的屬性,這里只以文件名作為示例,其他屬性不具體探,具體定義可以查找文檔。
#include <dirent.h> struct dirent *readdir(DIR *dirp);
Linux下的方法依然很簡單,通過第一步得到的DIR*作為輸入參數,方法成功則返回指向當前文件的dirent*,struct dirent即保存文件信息的結構
struct dirent { ino_t d_ino; /* Inode number */ off_t d_off; /* Not an offset; see below */ unsigned short d_reclen; /* Length of this record */ unsigned char d_type; /* Type of file; not supported by all filesystem types */ char d_name[256]; /* Null-terminated filename */ };
如果讀取失敗則返回空指針NULL(0)
再看Windows下的方法
int _findnext( intptr_t handle, struct _finddata_t *fileinfo );
這里返回值是int,依舊是出錯時返回-1。(C沒有異常處理機制,而是采用朴素的錯誤碼機制,於是誕生了讓初學者感到很困惑的問題:返回0到底是代表正確還是不正確呢?對錯誤碼而言,0往往代表正確,而對指針而言0則代表失敗,也就是空指針NULL)
第1個輸入參數也是第1個函數的返回值,第2個輸入參數也是描述文件的結構體的指針。struct _finddata_t即保存文件信息的結構,它也是個跨(Windows)平台的定義,以32位系統為例
struct _finddata32_t { unsigned attrib; __time32_t time_create; // -1 for FAT file systems __time32_t time_access; // -1 for FAT file systems __time32_t time_write; _fsize_t size; char name[260]; };
3、關閉目錄
#include <sys/types.h> #include <dirent.h> int closedir(DIR *dirp);
Linux下的,輸入參數是第1個函數的返回值,而這里返回值不再是指針,而是錯誤碼,因此返回值為0時代表關閉directory stream成功,為-1代表失敗
int _findclose( intptr_t handle );
Windows下的也一樣,輸入參數是第1個函數的返回值,返回0代表關閉handle成功,為-1代表失敗。
最后分別給出Linux和Windows上遍歷目錄下所有文件的示例代碼(為了簡化忽略錯誤處理)
#include <stdio.h> #include <dirent.h> int main(int argc, char** argv) { struct dirent *direntp; DIR *dirp = opendir("/"); if (dirp != NULL) { while ((direntp = readdir(dirp)) != NULL) printf("%s\n", direntp->d_name); } closedir(dirp); return 0; }
// Windows(C++) #include <stdio.h> #include <stdlib.h> #include <io.h> // windows的CRT庫 #include <string> int main() { _finddata_t fd; intptr_t handle; std::string dir_name = "C:\\"; if ((handle = _findfirst((dir_name + "*.*").c_str(), &fd)) != -1) { while (_findnext(handle, &fd) != -1) printf("%s\n", fd.name); } _findclose(handle); return 0; }
由於Windows下還要對dir_name附上一段字符串所以直接用std::string了,用char數組然后strcpy太麻煩。
對比可以發現,Windows是把文件結構的指針作為輸入參數,而Linux則是作為返回參數,Linux下的這種做法更為自然,而且即使用的是C風格,代碼也非常簡單易懂。
-----------------------------------------------------------下面是之前的錯誤看法--------------------------------------------------------------
但是要注意,Linux這種做法實際上是動態申請了空間,需要手動free(direntp)來釋放內存,雖然APUE上面的示例代碼並沒有這一步。
-----------------------------------------------------------上面是之前的錯誤看法--------------------------------------------------------------
readdir是不可重入的函數,按照man的說明,readdir()的返回值會被接下來的調用給重寫
The data returned by readdir() may be overwritten by subsequent calls
to readdir() for the same directory stream.
也就是說readdir的實現其實是類似這種
struct dirent *readdir(DIR *dp) { static struct dirent dir; // ... return &dir; }
而不是使用動態申請內存然后再返回。修改上面列出的Linux遍歷目錄的示例代碼,在while ((dirent p = readdir(dirp)) != NULL)的循環體內printf后面加一句delete dirp或者free(dirp)都會報錯,而且是特別嚴重的Aborted (core dumped)。
看來當初自作聰明以為APUE上沒顧及到內存的釋放的我還是太嫩了,繼續啃APUE~