所謂“文件”是指一組相關(guān)數(shù)據(jù)的有序集合。 這個數(shù)據(jù)集有一個名稱,叫做文件名。 實際上在前面的各章中我們已經(jīng)多次使用了文件,例如源程序文件、目標(biāo)文件、可執(zhí)行文件、庫文件 (頭文件)等。文件通常是駐留在外部介質(zhì)(如磁盤等)上的, 在使用時才調(diào)入內(nèi)存中來。從不同的角度可對文件作不同的分類。從用戶的角度看,文件可分為普通文件和設(shè)備文件兩種。
普通文件是指駐留在磁盤或其它外部介質(zhì)上的一個有序數(shù)據(jù)集,可以是源文件、目標(biāo)文件、可執(zhí)行程序; 也可以是一組待輸入處理的原始數(shù)據(jù),或者是一組輸出的結(jié)果。對于源文件、目標(biāo)文件、 可執(zhí)行程序可以稱作程序文件,對輸入輸出數(shù)據(jù)可稱作數(shù)據(jù)文件。
設(shè)備文件是指與主機(jī)相聯(lián)的各種外部設(shè)備,如顯示器、打印機(jī)、鍵盤等。在操作系統(tǒng)中,把外部設(shè)備也看作是一個文件來進(jìn)行管理,把它們的輸入、輸出等同于對磁盤文件的讀和寫。 通常把顯示器定義為標(biāo)準(zhǔn)輸出文件, 一般情況下在屏幕上顯示有關(guān)信息就是向標(biāo)準(zhǔn)輸出文件輸出。如前面經(jīng)常使用的printf,putchar 函數(shù)就是這類輸出。鍵盤通常被指定標(biāo)準(zhǔn)的輸入文件, 從鍵盤上輸入就意味著從標(biāo)準(zhǔn)輸入文件上輸入數(shù)據(jù)。scanf,getchar函數(shù)就屬于這類輸入。
從文件編碼的方式來看,文件可分為ASCII碼文件和二進(jìn)制碼文件兩種。
ASCII文件也稱為文本文件,這種文件在磁盤中存放時每個字符對應(yīng)一個字節(jié),用于存放對應(yīng)的ASCII碼。例如,數(shù)5678的存儲形式為:
ASC碼: 00110101 00110110 00110111 00111000
↓ ↓ ↓ ↓
十進(jìn)制碼: 5 6 7 8 共占用4個字節(jié)。ASCII碼文件可在屏幕上按字符顯示, 例如源程序文件就是ASCII文件,用DOS命令TYPE可顯示文件的內(nèi)容。 由于是按字符顯示,因此能讀懂文件內(nèi)容。
二進(jìn)制文件是按二進(jìn)制的編碼方式來存放文件的。 例如, 數(shù)5678的存儲形式為: 00010110 00101110只占二個字節(jié)。二進(jìn)制文件雖然也可在屏幕上顯示, 但其內(nèi)容無法讀懂。C系統(tǒng)在處理這些文件時,并不區(qū)分類型,都看成是字符流,按字節(jié)進(jìn)行處理。 輸入輸出字符流的開始和結(jié)束只由程序控制而不受物理符號(如回車符)的控制。 因此也把這種文件稱作“流式文件”。
本章討論流式文件的打開、關(guān)閉、讀、寫、 定位等各種操作。文件指針在C語言中用一個指針變量指向一個文件, 這個指針稱為文件指針。通過文件指針就可對它所指的文件進(jìn)行各種操作。 定義說明文件指針的一般形式為: FILE* 指針變量標(biāo)識符; 其中FILE應(yīng)為大寫,它實際上是由系統(tǒng)定義的一個結(jié)構(gòu), 該結(jié)構(gòu)中含有文件名、文件狀態(tài)和文件當(dāng)前位置等信息。 在編寫源程序時不必關(guān)心FILE結(jié)構(gòu)的細(xì)節(jié)。例如:FILE *fp; 表示fp是指向FILE結(jié)構(gòu)的指針變量,通過fp 即可找存放某個文件信息的結(jié)構(gòu)變量,然后按結(jié)構(gòu)變量提供的信息找到該文件, 實施對文件的操作。習(xí)慣上也籠統(tǒng)地把fp稱為指向一個文件的指針。文件的打開與關(guān)閉文件在進(jìn)行讀寫操作之前要先打開,使用完畢要關(guān)閉。 所謂打開文件,實際上是建立文件的各種有關(guān)信息, 并使文件指針指向該文件,以便進(jìn)行其它操作。關(guān)閉文件則斷開指針與文件之間的聯(lián)系,也就禁止再對該文件進(jìn)行操作。
在C語言中,文件操作都是由庫函數(shù)來完成的。 在本章內(nèi)將介紹主要的文件操作函數(shù)。
文件打開函數(shù)fopen
fopen函數(shù)用來打開一個文件,其調(diào)用的一般形式為: 文件指針名=fopen(文件名,使用文件方式) 其中,“文件指針名”必須是被說明為FILE 類型的指針變量,“文件名”是被打開文件的文件名。 “使用文件方式”是指文件的類型和操作要求。“文件名”是字符串常量或字符串?dāng)?shù)組。例如:
FILE *fp;
fp=("file a","r");
其意義是在當(dāng)前目錄下打開文件file a, 只允許進(jìn)行“讀”操作,并使fp指向該文件。
又如:
FILE *fphzk
fphzk=("c:\\hzk16',"rb")
其意義是打開C驅(qū)動器磁盤的根目錄下的文件hzk16, 這是一個二進(jìn)制文件,只允許按二進(jìn)制方式進(jìn)行讀操作。兩個反斜線“\\ ”中的第一個表示轉(zhuǎn)義字符,第二個表示根目錄。使用文件的方式共有12種,下面給出了它們的符號和意義。
文件使用方式 意 義
“rt” 只讀打開一個文本文件,只允許讀數(shù)據(jù)
“wt” 只寫打開或建立一個文本文件,只允許寫數(shù)據(jù)
“at” 追加打開一個文本文件,并在文件末尾寫數(shù)據(jù)
“rb” 只讀打開一個二進(jìn)制文件,只允許讀數(shù)據(jù)
“wb” 只寫打開或建立一個二進(jìn)制文件,只允許寫數(shù)據(jù)
“ab” 追加打開一個二進(jìn)制文件,并在文件末尾寫數(shù)據(jù)
“rt+” 讀寫打開一個文本文件,允許讀和寫
“wt+” 讀寫打開或建立一個文本文件,允許讀寫
“at+” 讀寫打開一個文本文件,允許讀,或在文件末追加數(shù) 據(jù)
“rb+” 讀寫打開一個二進(jìn)制文件,允許讀和寫
“wb+” 讀寫打開或建立一個二進(jìn)制文件,允許讀和寫
“ab+” 讀寫打開一個二進(jìn)制文件,允許讀,或在文件末追加數(shù)據(jù)
對于文件使用方式有以下幾點說明:
1. 文件使用方式由r,w,a,t,b,+六個字符拼成,各字符的含義是:
r(read): 讀
w(write): 寫
a(append): 追加
t(text): 文本文件,可省略不寫
b(banary): 二進(jìn)制文件
+: 讀和寫
2. 凡用“r”打開一個文件時,該文件必須已經(jīng)存在, 且只能從該文件讀出。
3. 用“w”打開的文件只能向該文件寫入。 若打開的文件不存在,則以指定的文件名建立該文件,若打開的文件已經(jīng)存在,則將該文件刪去,重建一個新文件。
4. 若要向一個已存在的文件追加新的信息,只能用“a ”方式打開文件。但此時該文件必須是存在的,否則將會出錯。
5. 在打開一個文件時,如果出錯,fopen將返回一個空指針值NULL。在程序中可以用這一信息來判別是否完成打開文件的工作,并作相應(yīng)的處理。因此常用以下程序段打開文件:
if((fp=fopen("c:\\hzk16","rb")==NULL)
{
printf("\nerror on open c:\\hzk16 file!");
getch();
exit(1);
}
這段程序的意義是,如果返回的指針為空,表示不能打開C盤根目錄下的hzk16文件,則給出提示信息“error on open c:\ hzk16file!”,下一行g(shù)etch()的功能是從鍵盤輸入一個字符,但不在屏幕上顯示。在這里,該行的作用是等待, 只有當(dāng)用戶從鍵盤敲任一鍵時,程序才繼續(xù)執(zhí)行, 因此用戶可利用這個等待時間閱讀出錯提示。敲鍵后執(zhí)行exit(1)退出程序。
6. 把一個文本文件讀入內(nèi)存時,要將ASCII碼轉(zhuǎn)換成二進(jìn)制碼, 而把文件以文本方式寫入磁盤時,也要把二進(jìn)制碼轉(zhuǎn)換成ASCII碼,因此文本文件的讀寫要花費較多的轉(zhuǎn)換時間。對二進(jìn)制文件的讀寫不存在這種轉(zhuǎn)換。
7. 標(biāo)準(zhǔn)輸入文件(鍵盤),標(biāo)準(zhǔn)輸出文件(顯示器 ),標(biāo)準(zhǔn)出錯輸出(出錯信息)是由系統(tǒng)打開的,可直接使用。文件關(guān)閉函數(shù)fClose文件一旦使用完畢,應(yīng)用關(guān)閉文件函數(shù)把文件關(guān)閉, 以避免文件的數(shù)據(jù)丟失等錯誤。
fclose函數(shù)
調(diào)用的一般形式是: fclose(文件指針); 例如:
fclose(fp); 正常完成關(guān)閉文件操作時,fclose函數(shù)返回值為0。如返回非零值則表示有錯誤發(fā)生。文件的讀寫對文件的讀和寫是最常用的文件操作。
在C語言中提供了多種文件讀寫的函數(shù):
·字符讀寫函數(shù) :fgetc和fputc
·字符串讀寫函數(shù):fgets和fputs
·數(shù)據(jù)塊讀寫函數(shù):freed和fwrite
·格式化讀寫函數(shù):fscanf和fprinf
下面分別予以介紹。使用以上函數(shù)都要求包含頭文件stdio.h。字符讀寫函數(shù)fgetC和fputC字符讀寫函數(shù)是以字符(字節(jié))為單位的讀寫函數(shù)。 每次可從文件讀出或向文件寫入一個字符。
一、讀字符函數(shù)fgetc
fgetc函數(shù)的功能是從指定的文件中讀一個字符,函數(shù)調(diào)用的形式為: 字符變量=fgetc(文件指針); 例如:ch=fgetc(fp);其意義是從打開的文件fp中讀取一個字符并送入ch中。
對于fgetc函數(shù)的使用有以下幾點說明:
1. 在fgetc函數(shù)調(diào)用中,讀取的文件必須是以讀或讀寫方式打開的。
2. 讀取字符的結(jié)果也可以不向字符變量賦值,例如:fgetc(fp);但是讀出的字符不能保存。
3. 在文件內(nèi)部有一個位置指針。用來指向文件的當(dāng)前讀寫字節(jié)。在文件打開時,該指針總是指向文件的第一個字節(jié)。使用fgetc 函數(shù)后, 該位置指針將向后移動一個字節(jié)。 因此可連續(xù)多次使用fgetc函數(shù),讀取多個字符。 應(yīng)注意文件指針和文件內(nèi)部的位置指針不是一回事。文件指針是指向整個文件的,須在程序中定義說明,只要不重新賦值,文件指針的值是不變的。文件內(nèi)部的位置指針用以指示文件內(nèi)部的當(dāng)前讀寫位置,每讀寫一次,該指針均向后移動,它不需在程序中定義說明,而是由系統(tǒng)自動設(shè)置的。
[例10.1]讀入文件e10-1.c,在屏幕上輸出。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
ch=fgetc(fp);
while (ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}
本例程序的功能是從文件中逐個讀取字符,在屏幕上顯示。 程序定義了文件指針fp,以讀文本文件方式打開文件“e10_1.c”, 并使fp指向該文件。如打開文件出錯, 給出提示并退出程序。程序第12行先讀出一個字符,然后進(jìn)入循環(huán), 只要讀出的字符不是文件結(jié)束標(biāo)志(每個文件末有一結(jié)束標(biāo)志EOF)就把該字符顯示在屏幕上,再讀入下一字符。每讀一次,文件內(nèi)部的位置指針向后移動一個字符,文件結(jié)束時,該指針指向EOF。執(zhí)行本程序?qū)@示整個文件。
二、寫字符函數(shù)fputc
fputc函數(shù)的功能是把一個字符寫入指定的文件中,函數(shù)調(diào)用的 形式為: fputc(字符量,文件指針); 其中,待寫入的字符量可以是字符常量或變量,例如:fputc('a',fp);其意義是把字符a寫入fp所指向的文件中。
對于fputc函數(shù)的使用也要說明幾點:
1. 被寫入的文件可以用、寫、讀寫,追加方式打開,用寫或讀寫方式打開一個已存在的文件時將清除原有的文件內(nèi)容,寫入字符從文件首開始。如需保留原有文件內(nèi)容,希望寫入的字符以文件末開始存放,必須以追加方式打開文件。被寫入的文件若不存在,則創(chuàng)建該文件。
2. 每寫入一個字符,文件內(nèi)部位置指針向后移動一個字節(jié)。
3. fputc函數(shù)有一個返回值,如寫入成功則返回寫入的字符, 否則返回一個EOF??捎么藖砼袛鄬懭胧欠癯晒Α?br />
[例10.2]從鍵盤輸入一行字符,寫入一個文件, 再把該文件內(nèi)容讀出顯示在屏幕上。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("string","wt+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("input a string:\n");
ch=getchar();
while (ch!='\n')
{
fputc(ch,fp);
ch=getchar();
}
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
程序中第6行以讀寫文本文件方式打開文件string。程序第13行從鍵盤讀入一個字符后進(jìn)入循環(huán),當(dāng)讀入字符不為回車符時, 則把該字符寫入文件之中,然后繼續(xù)從鍵盤讀入下一字符。 每輸入一個字符,文件內(nèi)部位置指針向后移動一個字節(jié)。寫入完畢, 該指針已指向文件末。如要把文件從頭讀出,須把指針移向文件頭, 程序第19行rewind函數(shù)用于把fp所指文件的內(nèi)部位置指針移到文件頭。 第20至25行用于讀出文件中的一行內(nèi)容。
[例10.3]把命令行參數(shù)中的前一個文件名標(biāo)識的文件, 復(fù)制到后一個文件名標(biāo)識的文件中, 如命令行中只有一個文件名則把該文件寫到標(biāo)準(zhǔn)輸出文件(顯示器)中。
#include<stdio.h>
main(int argc,char *argv[])
{
FILE *fp1,*fp2;
char ch;
if(argc==1)
{
printf("have not enter file name strike any key exit");
getch();
exit(0);
}
if((fp1=fopen(argv[1],"rt"))==NULL)
{
printf("Cannot open %s\n",argv[1]);
getch();
exit(1);
}
if(argc==2) fp2=stdout;
else if((fp2=fopen(argv[2],"wt+"))==NULL)
{
printf("Cannot open %s\n",argv[1]);
getch();
exit(1);
}
while((ch=fgetc(fp1))!=EOF)
fputc(ch,fp2);
fclose(fp1);
fclose(fp2);
}
本程序為帶參的main函數(shù)。程序中定義了兩個文件指針 fp1 和fp2,分別指向命令行參數(shù)中給出的文件。如命令行參數(shù)中沒有給出文件名,則給出提示信息。程序第18行表示如果只給出一個文件名,則使fp2指向標(biāo)準(zhǔn)輸出文件(即顯示器)。程序第25行至28行用循環(huán)語句逐個讀出文件1中的字符再送到文件2中。再次運行時,給出了一個文件名(由例10.2所建立的文件), 故輸出給標(biāo)準(zhǔn)輸出文件stdout,即在顯示器上顯示文件內(nèi)容。第三次運行,給出了二個文件名,因此把string中的內(nèi)容讀出,寫入到OK之中??捎肈OS命令type顯示OK的內(nèi)容:
********************************************************************************************
字符串讀寫函數(shù)fgets和fputs
一、讀字符串函數(shù)fgets函數(shù)的功能是從指定的文件中讀一個字符串到字符數(shù)組中,函數(shù)調(diào)用的形式為: fgets(字符數(shù)組名,n,文件指針); 其中的n是一個正整數(shù)。表示從文件中讀出的字符串不超過 n-1個字符。在讀入的最后一個字符后加上串結(jié)束標(biāo)志'\0'。例如:fgets(str,n,fp);的意義是從fp所指的文件中讀出n-1個字符送入字符數(shù)組str中。
[例10.4]從e10_1.c文件中讀入一個含10個字符的字符串。
#include<stdio.h>
main()
{
FILE *fp;
char str[11];
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
fgets(str,11,fp);
printf("%s",str);
fclose(fp);
}
本例定義了一個字符數(shù)組str共11個字節(jié),在以讀文本文件方式打開文件e101.c后,從中讀出10個字符送入str數(shù)組,在數(shù)組最后一個單元內(nèi)將加上'\0',然后在屏幕上顯示輸出str數(shù)組。輸出的十個字符正是例10.1程序的前十個字符。
對fgets函數(shù)有兩點說明:
1. 在讀出n-1個字符之前,如遇到了換行符或EOF,則讀出結(jié)束。
2. fgets函數(shù)也有返回值,其返回值是字符數(shù)組的首地址。
二、寫字符串函數(shù)fputs
fputs函數(shù)的功能是向指定的文件寫入一個字符串,其調(diào)用形式為: fputs(字符串,文件指針) 其中字符串可以是字符串常量,也可以是字符數(shù)組名, 或指針 變量,例如:
fputs(“abcd“,fp);
其意義是把字符串“abcd”寫入fp所指的文件之中。[例10.5]在例10.2中建立的文件string中追加一個字符串。
#include<stdio.h>
main()
{
FILE *fp;
char ch,st[20];
if((fp=fopen("string","at+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("input a string:\n");
scanf("%s",st);
fputs(st,fp);
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
本例要求在string文件末加寫字符串,因此,在程序第6行以追加讀寫文本文件的方式打開文件string 。 然后輸入字符串, 并用fputs函數(shù)把該串寫入文件string。在程序15行用rewind函數(shù)把文件內(nèi)部位置指針移到文件首。 再進(jìn)入循環(huán)逐個顯示當(dāng)前文件中的全部內(nèi)容。
數(shù)據(jù)塊讀寫函數(shù)fread和fwrite
C語言還提供了用于整塊數(shù)據(jù)的讀寫函數(shù)。 可用來讀寫一組數(shù)據(jù),如一個數(shù)組元素,一個結(jié)構(gòu)變量的值等。讀數(shù)據(jù)塊函數(shù)調(diào)用的一般形式為: fread(buffer,size,count,fp); 寫數(shù)據(jù)塊函數(shù)調(diào)用的一般形式為: fwrite(buffer,size,count,fp); 其中buffer是一個指針,在fread函數(shù)中,它表示存放輸入數(shù)據(jù)的首地址。在fwrite函數(shù)中,它表示存放輸出數(shù)據(jù)的首地址。 size 表示數(shù)據(jù)塊的字節(jié)數(shù)。count 表示要讀寫的數(shù)據(jù)塊塊數(shù)。fp 表示文件指針。
例如:
fread(fa,4,5,fp); 其意義是從fp所指的文件中,每次讀4個字節(jié)(一個實數(shù))送入實數(shù)組fa中,連續(xù)讀5次,即讀5個實數(shù)到fa中。
[例10.6]從鍵盤輸入兩個學(xué)生數(shù)據(jù),寫入一個文件中, 再讀出這兩個學(xué)生的數(shù)據(jù)顯示在屏幕上。
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen("stu_list","wb+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
fwrite(pp,sizeof(struct stu),2,fp);
rewind(fp);
fread(qq,sizeof(struct stu),2,fp);
printf("\n\nname\tnumber age addr\n");
for(i=0;i<2;i++,qq++)
printf("%s\t%5d%7d%s\n",qq->name,qq->num,qq->age,qq->addr);
fclose(fp);
}
本例程序定義了一個結(jié)構(gòu)stu,說明了兩個結(jié)構(gòu)數(shù)組boya和 boyb以及兩個結(jié)構(gòu)指針變量pp和qq。pp指向boya,qq指向boyb。程序第16行以讀寫方式打開二進(jìn)制文件“stu_list”,輸入二個學(xué)生數(shù)據(jù)之后,寫入該文件中, 然后把文件內(nèi)部位置指針移到文件首,讀出兩塊學(xué)生數(shù)據(jù)后,在屏幕上顯示。
格式化讀寫函數(shù)fscanf和fprintf
fscanf函數(shù),fprintf函數(shù)與前面使用的scanf和printf 函數(shù)的功能相似,都是格式化讀寫函數(shù)。 兩者的區(qū)別在于 fscanf 函數(shù)和fprintf函數(shù)的讀寫對象不是鍵盤和顯示器,而是磁盤文件。這兩個函數(shù)的調(diào)用格式為: fscanf(文件指針,格式字符串,輸入表列); fprintf(文件指針,格式字符串,輸出表列); 例如:
fscanf(fp,"%d%s",&i,s);
fprintf(fp,"%d%c",j,ch);
用fscanf和fprintf函數(shù)也可以完成例10.6的問題。修改后的程序如例10.7所示。
[例10.7]
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen("stu_list","wb+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
for(i=0;i<2;i++,pp++)
fprintf(fp,"%s %d %d %s\n",pp->name,pp->num,pp->age,pp->
addr);
rewind(fp);
for(i=0;i<2;i++,qq++)
fscanf(fp,"%s %d %d %s\n",qq->name,&qq->num,&qq->age,qq->addr);
printf("\n\nname\tnumber age addr\n");
qq=boyb;
for(i=0;i<2;i++,qq++)
printf("%s\t%5d %7d %s\n",qq->name,qq->num, qq->age,
qq->addr);
fclose(fp);
}
與例10.6相比,本程序中fscanf和fprintf函數(shù)每次只能讀寫一個結(jié)構(gòu)數(shù)組元素,因此采用了循環(huán)語句來讀寫全部數(shù)組元素。 還要注意指針變量pp,qq由于循環(huán)改變了它們的值,因此在程序的25和32行分別對它們重新賦予了數(shù)組的首地址。
文件的隨機(jī)讀寫
前面介紹的對文件的讀寫方式都是順序讀寫, 即讀寫文件只能從頭開始,順序讀寫各個數(shù)據(jù)。 但在實際問題中常要求只讀寫文件中某一指定的部分。 為了解決這個問題可移動文件內(nèi)部的位置指針到需要讀寫的位置,再進(jìn)行讀寫,這種讀寫稱為隨機(jī)讀寫。 實現(xiàn)隨機(jī)讀寫的關(guān)鍵是要按要求移動位置指針,這稱為文件的定位。文件定位移動文件內(nèi)部位置指針的函數(shù)主要有兩個, 即 rewind 函數(shù)和fseek函數(shù)。
rewind函數(shù)前面已多次使用過,其調(diào)用形式為: rewind(文件指針); 它的功能是把文件內(nèi)部的位置指針移到文件首。 下面主要介紹
fseek函數(shù)。
fseek函數(shù)用來移動文件內(nèi)部位置指針,其調(diào)用形式為: fseek(文件指針,位移量,起始點); 其中:“文件指針”指向被移動的文件。 “位移量”表示移動的字節(jié)數(shù),要求位移量是long型數(shù)據(jù),以便在文件長度大于64KB 時不會出錯。當(dāng)用常量表示位移量時,要求加后綴“L”?!捌鹗键c”表示從何處開始計算位移量,規(guī)定的起始點有三種:文件首,當(dāng)前位置和文件尾。
其表示方法如表10.2。
起始點 表示符號 數(shù)字表示
──────────────────────────
文件首 SEEK—SET 0
當(dāng)前位置 SEEK—CUR 1
文件末尾 SEEK—END 2
例如:
fseek(fp,100L,0);其意義是把位置指針移到離文件首100個字節(jié)處。還要說明的是fseek函數(shù)一般用于二進(jìn)制文件。在文本文件中由于要進(jìn)行轉(zhuǎn)換,故往往計算的位置會出現(xiàn)錯誤。文件的隨機(jī)讀寫在移動位置指針之后, 即可用前面介紹的任一種讀寫函數(shù)進(jìn)行讀寫。由于一般是讀寫一個數(shù)據(jù)據(jù)塊,因此常用fread和fwrite函數(shù)。下面用例題來說明文件的隨機(jī)讀寫。
[例10.8]在學(xué)生文件stu list中讀出第二個學(xué)生的數(shù)據(jù)。
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boy,*qq;
main()
{
FILE *fp;
char ch;
int i=1;
qq=&boy;
if((fp=fopen("stu_list","rb"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
rewind(fp);
fseek(fp,i*sizeof(struct stu),0);
fread(qq,sizeof(struct stu),1,fp);
printf("\n\nname\tnumber age addr\n");
printf("%s\t%5d %7d %s\n",qq->name,qq->num,qq->age,
qq->addr);
}
文件stu_list已由例10.6的程序建立,本程序用隨機(jī)讀出的方法讀出第二個學(xué)生的數(shù)據(jù)。程序中定義boy為stu類型變量,qq為指向boy的指針。以讀二進(jìn)制文件方式打開文件,程序第22行移動文件位置指針。其中的i值為1,表示從文件頭開始,移動一個stu類型的長度, 然后再讀出的數(shù)據(jù)即為第二個學(xué)生的數(shù)據(jù)。
文件檢測函數(shù)
C語言中常用的文件檢測函數(shù)有以下幾個。
一、文件結(jié)束檢測函數(shù)feof函數(shù)調(diào)用格式: feof(文件指針);
功能:判斷文件是否處于文件結(jié)束位置,如文件結(jié)束,則返回值為1,否則為0。
二、讀寫文件出錯檢測函數(shù)ferror函數(shù)調(diào)用格式: ferror(文件指針);
功能:檢查文件在用各種輸入輸出函數(shù)進(jìn)行讀寫時是否出錯。 如ferror返回值為0表示未出錯,否則表示有錯。
三、文件出錯標(biāo)志和文件結(jié)束標(biāo)志置0函數(shù)clearerr函數(shù)調(diào)用格式: clearerr(文件指針);
功能:本函數(shù)用于清除出錯標(biāo)志和文件結(jié)束標(biāo)志,使它們?yōu)?值。
C庫文件
C系統(tǒng)提供了豐富的系統(tǒng)文件,稱為庫文件,C的庫文件分為兩類,一類是擴(kuò)展名為".h"的文件,稱為頭文件, 在前面的包含命令中我們已多次使用過。在".h"文件中包含了常量定義、 類型定義、宏定義、函數(shù)原型以及各種編譯選擇設(shè)置等信息。另一類是函數(shù)庫,包括了各種函數(shù)的目標(biāo)代碼,供用戶在程序中調(diào)用。 通常在程序中調(diào)用一個庫函數(shù)時,要在調(diào)用之前包含該函數(shù)原型所在的".h" 文件。
在附錄中給出了全部庫函數(shù)。
ALLOC.H 說明內(nèi)存管理函數(shù)(分配、釋放等)。
ASSERT.H 定義 assert調(diào)試宏。
BIOS.H 說明調(diào)用IBM—PC ROM BIOS子程序的各個函數(shù)。
CONIO.H 說明調(diào)用DOS控制臺I/O子程序的各個函數(shù)。
CTYPE.H 包含有關(guān)字符分類及轉(zhuǎn)換的名類信息(如 isalpha和toascii等)。
DIR.H 包含有關(guān)目錄和路徑的結(jié)構(gòu)、宏定義和函數(shù)。
DOS.H 定義和說明MSDOS和8086調(diào)用的一些常量和函數(shù)。
ERRON.H 定義錯誤代碼的助記符。
FCNTL.H 定義在與open庫子程序連接時的符號常量。
FLOAT.H 包含有關(guān)浮點運算的一些參數(shù)和函數(shù)。
GRAPHICS.H 說明有關(guān)圖形功能的各個函數(shù),圖形錯誤代碼的常量定義,正對不同驅(qū)動程序的各種顏色值,及函數(shù)用到的一些特殊結(jié)構(gòu)。
IO.H 包含低級I/O子程序的結(jié)構(gòu)和說明。
LIMIT.H 包含各環(huán)境參數(shù)、編譯時間限制、數(shù)的范圍等信息。
MATH.H 說明數(shù)學(xué)運算函數(shù),還定了 HUGE VAL 宏, 說明了matherr和matherr子程序用到的特殊結(jié)構(gòu)。
MEM.H 說明一些內(nèi)存操作函數(shù)(其中大多數(shù)也在STRING.H 中說明)。
PROCESS.H 說明進(jìn)程管理的各個函數(shù),spawn…和EXEC …函數(shù)的結(jié)構(gòu)說明。
SETJMP.H 定義longjmp和setjmp函數(shù)用到的jmp buf類型, 說明這兩個函數(shù)。
SHARE.H 定義文件共享函數(shù)的參數(shù)。
SIGNAL.H 定義SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,說明rajse和signal兩個函數(shù)。
STDARG.H 定義讀函數(shù)參數(shù)表的宏。(如vprintf,vscarf函數(shù))。
STDDEF.H 定義一些公共數(shù)據(jù)類型和宏。
STDIO.H 定義Kernighan和Ritchie在Unix System V 中定義的標(biāo)準(zhǔn)和擴(kuò)展的類型和宏。還定義標(biāo)準(zhǔn)I/O 預(yù)定義流:stdin,stdout和stderr,說明 I/O流子程序。
STDLIB.H 說明一些常用的子程序:轉(zhuǎn)換子程序、搜索/ 排序子程序等。
STRING.H 說明一些串操作和內(nèi)存操作函數(shù)。
SYS\STAT.H 定義在打開和創(chuàng)建文件時用到的一些符號常量。
SYS\TYPES.H 說明ftime函數(shù)和timeb結(jié)構(gòu)。
SYS\TIME.H 定義時間的類型time[ZZ(Z] [ZZ)]t。
TIME.H 定義時間轉(zhuǎn)換子程序asctime、localtime和gmtime的結(jié)構(gòu),ctime、 difftime、 gmtime、 localtime和stime用到的類型,并提供這些函數(shù)的原型。
VALUE.H 定義一些重要常量, 包括依賴于機(jī)器硬件的和為與Unix System V相兼容而說明的一些常量,包括浮點和雙精度值的范圍。
posted @
2006-05-31 13:33 季浩 閱讀(1358) |
評論 (1) |
編輯 收藏
?
UNIX系統(tǒng)為程序員提供了許多子程序,這些子程序可存取各種安全屬性.有
些是信息子程序,返回文件屬性,實際的和有效的UID,GID等信息.有些子程序可
改變文件屬性.UID,GID等有些處理口令文件和小組文件,還有些完成加密和解密.
本文主要討論有關(guān)系統(tǒng)子程序,標(biāo)準(zhǔn)C庫子程序的安全,如何寫安全的C程序
并從root的角度介紹程序設(shè)計(僅能被root調(diào)用的子程序).
1.系統(tǒng)子程序
(1)I/O子程序
*creat():建立一個新文件或重寫一個暫存文件.
需要兩個參數(shù):文件名和存取許可值(8進(jìn)制方式).如:
creat(”/usr/pat/read_write”,0666) /* 建立存取許可方式為0666的文件 */
調(diào)用此子程序的進(jìn)程必須要有建立的文件的所在目錄的寫和執(zhí)行許可,置
給creat()的許可方式變量將被umask()設(shè)置的文件建立屏蔽值所修改,新
文件的所有者和小組由有效的UID和GID決定.
返回值為新建文件的文件描述符.
*fstat():見后面的stat().
*open():在C程序內(nèi)部打開文件.
需要兩個參數(shù):文件路徑名和打開方式(I,O,I&O).
如果調(diào)用此子程序的進(jìn)程沒有對于要打開的文件的正確存取許可(包括文
件路徑上所有目錄分量的搜索許可),將會引起執(zhí)行失敗.
如果此子程序被調(diào)用去打開不存在的文件,除非設(shè)置了O_CREAT標(biāo)志,調(diào)用
將不成功.此時,新文件的存取許可作為第三個參數(shù)(可被用戶的umask修
改).
當(dāng)文件被進(jìn)程打開后再改變該文件或該文件所在目錄的存取許可,不影響
對該文件的I/O操作.
*read():從已由open()打開并用作輸入的文件中讀信息.
它并不關(guān)心該文件的存取許可.一旦文件作為輸入打開,即可從該文件中讀
取信息.
*write():輸出信息到已由open()打開并用作輸出的文件中.同read()一樣
它也不關(guān)心該文件的存取許可.
(2)進(jìn)程控制
*exec()族:包括execl(),execv(),execle(),execve(),execlp()和execvp()
可將一可執(zhí)行模快拷貝到調(diào)用進(jìn)程占有的存貯空間.正被調(diào)用進(jìn)
程執(zhí)行的程序?qū)⒉粡?fù)存在,新程序取代其位置.
這是UNIX系統(tǒng)中一個程序被執(zhí)行的唯一方式:用將執(zhí)行的程序復(fù)蓋原有的
程序.
安全注意事項:
. 實際的和有效的UID和GID傳遞給由exec()調(diào)入的不具有SUID和SGID許
可的程序.
. 如果由exec()調(diào)入的程序有SUID和SGID許可,則有效的UID和GID將設(shè)
置給該程序的所有者或小組.
. 文件建立屏蔽值將傳遞給新程序.
. 除設(shè)了對exec()關(guān)閉標(biāo)志的文件外,所有打開的文件都傳遞給新程序.
用fcntl()子程序可設(shè)置對exec()的關(guān)閉標(biāo)志.
*fork():用來建立新進(jìn)程.其建立的子進(jìn)程是與調(diào)用fork()的進(jìn)程(父進(jìn)程)
完全相同的拷貝(除了進(jìn)程號外)
安全注意事項:
. 子進(jìn)程將繼承父進(jìn)程的實際和有效的UID和GID.
. 子進(jìn)程繼承文件方式建立屏蔽值.
. 所有打開的文件傳給子進(jìn)程.
*signal():允許進(jìn)程處理可能發(fā)生的意外事件和中斷.
需要兩個參數(shù):信號編號和信號發(fā)生時要調(diào)用的子程序.
信號編號定義在signal.h中.
信號發(fā)生時要調(diào)用的子程序可由用戶編寫,也可用系統(tǒng)給的值,如:SIG_IGN
則信號將被忽略,SIG_DFL則信號將按系統(tǒng)的缺省方式處理.
如許多與安全有關(guān)的程序禁止終端發(fā)中斷信息(BREAK和DELETE),以免自己
被用戶終端終止運行.
有些信號使UNIX系統(tǒng)的產(chǎn)生進(jìn)程的核心轉(zhuǎn)儲(進(jìn)程接收到信號時所占內(nèi)存
的內(nèi)容,有時含有重要信息),此系統(tǒng)子程序可用于禁止核心轉(zhuǎn)儲.
(3)文件屬性
*access():檢測指定文件的存取能力是否符合指定的存取類型.
需要兩個參數(shù):文件名和要檢測的存取類型(整數(shù)).
存取類型定義如下:
0: 檢查文件是否存在
1: 檢查是否可執(zhí)行(搜索)
2: 檢查是否可寫
3: 檢查是否可寫和執(zhí)行
4: 檢查是否可讀
5: 檢查是否可讀和執(zhí)行
6: 檢查是否可讀可寫可執(zhí)行
這些數(shù)字的意義和chmod命令中規(guī)定許可方式的數(shù)字意義相同.
此子程序使用實際的UID和GID檢測文件的存取能力(一般有效的UID和GID
用于檢查文件存取能力).
返回值: 0:許可 -1:不許可.
*chmod():將指定文件或目錄的存取許可方式改成新的許可方式.
需要兩個參數(shù):文件名和新的存取許可方式.
*chown():同時改變指定文件的所有者和小組的UID和GID.(與chown命令不
同).
由于此子程序同時改變文件的所有者和小組,故必須取消所操作文件的SUID
和SGID許可,以防止用戶建立SUID和SGID程序,然后運行chown()去獲得別
人的權(quán)限.
*stat():返回文件的狀態(tài)(屬性).
需要兩個參數(shù):文件路徑名和一個結(jié)構(gòu)指針,指向狀態(tài)信息的存放
的位置.
結(jié)構(gòu)定義如下:
st_mode: 文件類型和存取許可方式
st_ino: I節(jié)點號
st_dev: 文件所在設(shè)備的ID
st_rdev: 特別文件的ID
st_nlink: 文件鏈接數(shù)
st_uid: 文件所有者的UID
st_gid: 文件小組的GID
st_size: 按字節(jié)計數(shù)的文件大小
st_atime: 最后存取時間(讀)
st_mtime: 最后修改時間(寫)和最后狀態(tài)的改變
st_ctime: 最后的狀態(tài)修改時間
返回值: 0:成功 1:失敗
*umask():將調(diào)用進(jìn)程及其子進(jìn)程的文件建立屏蔽值設(shè)置為指定的存取許可.
需要一個參數(shù): 新的文件建立屏值.
(4)UID和GID的處理
*getuid():返回進(jìn)程的實際UID.
*getgid():返回進(jìn)程的實際GID.
以上兩個子程序可用于確定是誰在運行進(jìn)程.
*geteuid():返回進(jìn)程的有效UID.
*getegid():返回進(jìn)程的有效GID.
以上兩個子程序可在一個程序不得不確定它是否在運行某用戶而不是運行
它的用戶的SUID程序時很有用,可調(diào)用它們來檢查確認(rèn)本程序的確是以該
用戶的SUID許可在運行.
*setuid():用于改變有效的UID.
對于一般用戶,此子程序僅對要在有效和實際的UID之間變換的SUID程序才
有用(從原有效UID變換為實際UID),以保護(hù)進(jìn)程不受到安全危害.實際上該
進(jìn)程不再是SUID方式運行.
*setgid():用于改變有效的GID.
2.標(biāo)準(zhǔn)C庫
(1)標(biāo)準(zhǔn)I/O
*fopen():打開一個文件供讀或?qū)?安全方面的考慮同open()一樣.
*fread(),getc(),fgetc(),gets(),scanf()和fscanf():從已由fopen()打
開供讀的文件中讀取信息.它們并不關(guān)心文件的存取許可.這一點
同read().
*fwrite(),put(),fputc(),puts,fputs(),printf(),fprintf():寫信息到
已由fopen()打開供寫的文件中.它們也不關(guān)心文件的存取許可.
同write().
*getpass():從終端上讀至多8個字符長的口令,不回顯用戶輸入的字符.
需要一個參數(shù): 提示信息.
該子程序?qū)⑻崾拘畔@示在終端上,禁止字符回顯功能,從/dev/tty讀取口
令,然后再恢復(fù)字符回顯功能,返回剛敲入的口令的指針.
*popen():將在(5)運行shell中介紹.
(2)/etc/passwd處理
有一組子程序可對/etc/passwd文件進(jìn)行方便的存取,可對文件讀取到入口
項或?qū)懶碌娜肟陧椈蚋碌鹊?
*getpwuid():從/etc/passwd文件中獲取指定的UID的入口項.
*getpwnam():對于指定的登錄名,在/etc/passwd文件檢索入口項.
以上兩個子程序返回一指向passwd結(jié)構(gòu)的指針,該結(jié)構(gòu)定義在
/usr/include/pwd.h中,定義如下:
struct passwd {
char * pw_name; /* 登錄名 */
char * pw_passwd; /* 加密后的口令 */
uid_t pw_uid; /* UID */
gid_t pw_gid; /* GID */
char * pw_age; /* 代理信息 */
char * pw_comment; /* 注釋 */
char * pw_gecos;
char * pw_dir; /* 主目錄 */
char * pw_shell; /* 使用的shell */
};
*getpwent(),setpwent(),endpwent():對口令文件作后續(xù)處理.
首次調(diào)用getpwent(),打開/etc/passwd并返回指向文件中第一個入口項的
指針,保持調(diào)用之間文件的打開狀態(tài).
再調(diào)用getpwent()可順序地返回口令文件中的各入口項.
調(diào)用setpwent()把口令文件的指針重新置為文件的開始處.
使用完口令文件后調(diào)用endpwent()關(guān)閉口令文件.
*putpwent():修改或增加/etc/passwd文件中的入口項.
此子程序?qū)⑷肟陧棇懙揭粋€指定的文件中,一般是一個臨時文件,直接寫口
令文件是很危險的.最好在執(zhí)行前做文件封鎖,使兩個程序不能同時寫一個
文件.算法如下:
. 建立一個獨立的臨時文件,即/etc/passnnn,nnn是PID號.
. 建立新產(chǎn)生的臨時文件和標(biāo)準(zhǔn)臨時文件/etc/ptmp的鏈,若建鏈?zhǔn)?
則為有人正在使用/etc/ptmp,等待直到/etc/ptmp可用為止或退出.
. 將/etc/passwd拷貝到/etc/ptmp,可對此文件做任何修改.
. 將/etc/passwd移到備份文件/etc/opasswd.
. 建立/etc/ptmp和/etc/passwd的鏈.
. 斷開/etc/passnnn與/etc/ptmp的鏈.
注意:臨時文件應(yīng)建立在/etc目錄,才能保證文件處于同一文件系統(tǒng)中,建
鏈才能成功,且臨時文件不會不安全.此外,若新文件已存在,即便建
鏈的是root用戶,也將失敗,從而保證了一旦臨時文件成功地建鏈后
沒有人能再插進(jìn)來干擾.當(dāng)然,使用臨時文件的程序應(yīng)確保清除所有
臨時文件,正確地捕捉信號.
(3)/etc/group的處理
有一組類似于前面的子程序處理/etc/group的信息,使用時必須用include
語句將/usr/include/grp.h文件加入到自己的程序中.該文件定義了group
結(jié)構(gòu),將由getgrnam(),getgrgid(),getgrent()返回group結(jié)構(gòu)指針.
*getgrnam():在/etc/group文件中搜索指定的小組名,然后返回指向小組入
口項的指針.
*getgrgid():類似于前一子程序,不同的是搜索指定的GID.
*getgrent():返回group文件中的下一個入口項.
*setgrent():將group文件的文件指針恢復(fù)到文件的起點.
*endgrent():用于完成工作后,關(guān)閉group文件.
*getuid():返回調(diào)用進(jìn)程的實際UID.
*getpruid():以getuid()返回的實際UID為參數(shù),確定與實際UID相應(yīng)的登錄
名,或指定一UID為參數(shù).
*getlogin():返回在終端上登錄的用戶的指針.
系統(tǒng)依次檢查STDIN,STDOUT,STDERR是否與終端相聯(lián),與終端相聯(lián)的標(biāo)準(zhǔn)輸
入用于確定終端名,終端名用于查找列于/etc/utmp文件中的用戶,該文件
由login維護(hù),由who程序用來確認(rèn)用戶.
*cuserid():首先調(diào)用getlogin(),若getlogin()返回NULL指針,再調(diào)用
getpwuid(getuid()).
*以下為命令:
*logname:列出登錄進(jìn)終端的用戶名.
*who am i:顯示出運行這條命令的用戶的登錄名.
*id:顯示實際的UID和GID(若有效的UID和GID和實際的不同時也顯示有效的
UID和GID)和相應(yīng)的登錄名.
(4)加密子程序
1977年1月,NBS宣布一個用于美國聯(lián)邦政府ADP系統(tǒng)的網(wǎng)絡(luò)的標(biāo)準(zhǔn)加密法:數(shù)
據(jù)加密標(biāo)準(zhǔn)即DES用于非機(jī)密應(yīng)用方面.DES一次處理64BITS的塊,56位的加
密鍵.
*setkey(),encrypt():提供用戶對DES的存取.
此兩子程序都取64BITS長的字符數(shù)組,數(shù)組中的每個元素代表一個位,為0
或1.setkey()設(shè)置將按DES處理的加密鍵,忽略每第8位構(gòu)成一個56位的加
密鍵.encrypt()然后加密或解密給定的64BITS長的一塊,加密或解密取決
于該子程序的第二個變元,0:加密 1:解密.
*crypt():是UNIX系統(tǒng)中的口令加密程序,也被/usr/lib/makekey命令調(diào)用.
crypt()子程序與crypt命令無關(guān),它與/usr/lib/makekey一樣取8個字符長
的關(guān)鍵詞,2個salt字符.關(guān)鍵詞送給setkey(),salt字符用于混合encrypt()
中的DES算法,最終調(diào)用encrypt()重復(fù)25次加密一個相同的字符串.
返回加密后的字符串指針.
(5)運行shell
*system():運行/bin/sh執(zhí)行其參數(shù)指定的命令,當(dāng)指定命令完成時返回.
*popen():類似于system(),不同的是命令運行時,其標(biāo)準(zhǔn)輸入或輸出聯(lián)到由
popen()返回的文件指針.
二者都調(diào)用fork(),exec(),popen()還調(diào)用pipe(),完成各自的工作,因而
fork()和exec()的安全方面的考慮開始起作用.
3.寫安全的C程序
一般有兩方面的安全問題,在寫程序時必須考慮:
(1)確保自己建立的任何臨時文件不含有機(jī)密數(shù)據(jù),如果有機(jī)密數(shù)據(jù),設(shè)置
臨時文件僅對自己可讀/寫.確保建立臨時文件的目錄僅對自己可寫.
(2)確保自己要運行的任何命令(通過system(),popen(),execlp(),
execvp()運行的命令)的確是自己要運行的命令,而不是其它什么命
令,尤其是自己的程序為SUID或SGID許可時要小心.
第一方面比較簡單,在程序開始前調(diào)用umask(077).若要使文件對其他人可
讀,可再調(diào)chmod(),也可用下述語名建立一個”不可見”的臨時文件.
creat(”/tmp/xxx”,0);
file=open(”/tmp/xxx”,O_RDWR);
unlink(”/tmp/xxx”);
文件/tmp/xxx建立后,打開,然后斷開鏈,但是分配給該文件的存儲器并未刪
除,直到最終指向該文件的文件通道被關(guān)閉時才被刪除.打開該文件的進(jìn)程
和它的任何子進(jìn)程都可存取這個臨時文件,而其它進(jìn)程不能存取該文件,因
為它在/tmp中的目錄項已被unlink()刪除.
第二方面比較復(fù)雜而微妙,由于system(),popen(),execlp(),execvp()執(zhí)行
時,若不給出執(zhí)行命令的全路徑,就能”騙”用戶的程序去執(zhí)行不同的命令.因
為系統(tǒng)子程序是根據(jù)PATH變量確定哪種順序搜索哪些目錄,以尋找指定的命
令,這稱為SUID陷井.最安全的辦法是在調(diào)用system()前將有效UID改變成實
際UID,另一種比較好的方法是以全路徑名命令作為參數(shù).execl(),execv(),
execle(),execve()都要求全路徑名作為參數(shù).有關(guān)SUID陷井的另一方式是
在程序中設(shè)置PATH,由于system()和popen()都啟動shell,故可使用shell句
法.如:
system(”PATH=/bin:/usr/bin cd”);
這樣允許用戶運行系統(tǒng)命令而不必知道要執(zhí)行的命令在哪個目錄中,但這種
方法不能用于execlp(),execvp()中,因為它們不能啟動shell執(zhí)行調(diào)用序列
傳遞的命令字符串.
關(guān)于shell解釋傳遞給system()和popen()的命令行的方式,有兩個其它的問
題:
*shell使用IFS shell變量中的字符,將命令行分解成單詞(通常這個
shell變量中是空格,tab,換行),如IFS中是/,字符串/bin/ed被解釋成單詞
bin,接下來是單詞ed,從而引起命令行的曲解.
再強(qiáng)調(diào)一次:在通過自己的程序運行另一個程序前,應(yīng)將有效UID改為實際的
UID,等另一個程序退出后,再將有效UID改回原來的有效UID.
SUID/SGID程序指導(dǎo)準(zhǔn)則
(1)不要寫SUID/SGID程序,大多數(shù)時候無此必要.
(2)設(shè)置SGID許可,不要設(shè)置SUID許可.應(yīng)獨自建立一個新的小組.
(3)不要用exec()執(zhí)行任何程序.記住exec()也被system()和popen()調(diào)用.
. 若要調(diào)用exec()(或system(),popen()),應(yīng)事先用setgid(getgid())
將有效GID置加實際GID.
. 若不能用setgid(),則調(diào)用system()或popen()時,應(yīng)設(shè)置IFS:
popen(”IFS=\t\n;export IFS;/bin/ls”,”r”);
. 使用要執(zhí)行的命令的全路徑名.
. 若不能使用全路徑名,則應(yīng)在命令前先設(shè)置PATH:
popen(”IFS=\t\n;export IFS;PATH=/bin:/usr/bin;/bin/ls”,”r”);
. 不要將用戶規(guī)定的參數(shù)傳給system()或popen();若無法避免則應(yīng)檢查
變元字符串中是否有特殊的shell字符.
. 若用戶有個大程序,調(diào)用exec()執(zhí)行許多其它程序,這種情況下不要將
大程序設(shè)置為SGID許可.可以寫一個(或多個)更小,更簡單的SGID程序
執(zhí)行必須具有SGID許可的任務(wù),然后由大程序執(zhí)行這些小SGID程序.
(4)若用戶必須使用SUID而不是SGID,以相同的順序記住(2),(3)項內(nèi)容,并
相應(yīng)調(diào)整.不要設(shè)置root的SUID許可.選一個其它戶頭.
(5)若用戶想給予其他人執(zhí)行自己的shell程序的許可,但又不想讓他們能
讀該程序,可將程序設(shè)置為僅執(zhí)行許可,并只能通過自己的shell程序來
運行.
編譯,安裝SUID/SGID程序時應(yīng)按下面的方法
(1)確保所有的SUID(SGID)程序是對于小組和其他用戶都是不可寫的,存取
權(quán)限的限制低于4755(2755)將帶來麻煩.只能更嚴(yán)格.4111(2111)將使
其他人無法尋找程序中的安全漏洞.
(2)警惕外來的編碼和make/install方法
. 某些make/install方法不加選擇地建立SUID/SGID程序.
. 檢查違背上述指導(dǎo)原則的SUID/SGID許可的編碼.
. 檢查makefile文件中可能建立SUID/SGID文件的命令.
4.root程序的設(shè)計
有若干個子程序可以從有效UID為0的進(jìn)程中調(diào)用.許多前面提到的子程序,
當(dāng)從root進(jìn)程中調(diào)用時,將完成和原來不同的處理.主要是忽略了許可權(quán)限的檢
查.
由root用戶運行的程序當(dāng)然是root進(jìn)程(SUID除外),因有效UID用于確定文
件的存取權(quán)限,所以從具有root的程序中,調(diào)用fork()產(chǎn)生的進(jìn)程,也是root進(jìn)程.
(1)setuid():從root進(jìn)程調(diào)用setuid()時,其處理有所不同,setuid()將把有
效的和實際的UID都置為指定的值.這個值可以是任何整型數(shù).而對非root
進(jìn)程則僅能以實際UID或本進(jìn)程原來有效的UID為變量值調(diào)用setuid().
(2)setgid():在系統(tǒng)進(jìn)程中調(diào)用setgid()時,與setuid()類似,將實際和有效
的GID都改變成其參數(shù)指定的值.
* 調(diào)用以上兩個子程序時,應(yīng)當(dāng)注意下面幾點:
. 調(diào)用一次setuid()(setgid())將同時設(shè)置有效和實際UID(GID),獨立分
別設(shè)置有效或?qū)嶋HUID(GID)固然很好,但無法做到這點.
. setuid()(setgid())可將有效和實際UID(GID)設(shè)置成任何整型數(shù),其數(shù)
值不必一定與/etc/passwd(/etc/group)中用戶(小組)相關(guān)聯(lián).
. 一旦程序以一個用戶的UID了setuid(),該程序就不再做為root運行,也
不可能再獲root特權(quán).
(3)chown():當(dāng)root進(jìn)程運行chown()時,chown()將不刪除文件的SUID和/或
SGID許可,但當(dāng)非root進(jìn)程運行chown()時,chown()將取消文件的SUID和/
或SGID許可.
(4)chroot():改變進(jìn)程對根目錄的概念,調(diào)用chroot()后,進(jìn)程就不能把當(dāng)前
工作目錄改變到新的根目錄以上的任一目錄,所有以/開始的路徑搜索,都
從新的根目錄開始.
(5)mknod():用于建立一個文件,類似于creat(),差別是mknod()不返回所打開
文件的文件描述符,并且能建立任何類型的文件(普通文件,特殊文件,目錄
文件).若從非root進(jìn)程調(diào)用mknod()將執(zhí)行失敗,只有建立FIFO特別文件
(有名管道文件)時例外,其它任何情況下,必須從root進(jìn)程調(diào)用mknod().由
于creat()僅能建立普通文件,mknod()是建立目錄文件的唯一途徑,因而僅
有root能建立目錄,這就是為什么mkdir命令具有SUID許可并屬root所有.
一般不從程序中調(diào)用mknod().通常用/etc/mknod命令建立特別設(shè)備文件而
這些文件一般不能在使用著時建立和刪除,mkdir命令用于建立目錄.當(dāng)用
mknod()建立特別文件時,應(yīng)當(dāng)注意確從所建的特別文件不允許存取內(nèi)存,
磁盤,終端和其它設(shè)備.
(6)unlink():用于刪除文件.參數(shù)是要刪除文件的路徑名指針.當(dāng)指定了目錄
時,必須從root進(jìn)程調(diào)用unlink(),這是必須從root進(jìn)程調(diào)用unlink()的唯
一情況,這就是為什么rmdir命令具有root的SGID許可的原因.
(7)mount(),umount():由root進(jìn)程調(diào)用,分別用于安裝和拆卸文件系統(tǒng).這兩
個子程序也被mount和umount命令調(diào)用,其參數(shù)基本和命令的參數(shù)相同.調(diào)
用mount(),需要給出一個特別文件和一個目錄的指針,特別文件上的文件
系統(tǒng)就將安裝在該目錄下,調(diào)用時還要給出一個標(biāo)識選項,指定被安裝的文
件系統(tǒng)要被讀/寫(0)還是僅讀(1).umount()的參數(shù)是要一個要拆卸的特別
文件的指針.
本文由isbase成員編譯或原創(chuàng),如要轉(zhuǎn)載請保持文章的完整性
posted @
2006-05-31 13:17 季浩 閱讀(490) |
評論 (0) |
編輯 收藏
摘要: C++中類的多態(tài)與虛函數(shù)的使用
出處:PConline
[ 2005-03-16 10:17:37 ]
作者:管寧 ...
閱讀全文
posted @
2006-05-05 17:37 季浩 閱讀(527) |
評論 (0) |
編輯 收藏
C/C+語言struct深層探索
|
出處:PConline |
|
[ 2005-08-11 10:30:50 ] |
作者:宋寶華 |
責(zé)任編輯:xietaoming |
1. struct的巨大作用
面對一個人的大型C/C++程序時,只看其對struct的使用情況我們就可以對其編寫者的編程經(jīng)驗進(jìn)行評估。因為一個大型的C/C++程序,勢必要涉及一些(甚至大量)進(jìn)行數(shù)據(jù)組合的結(jié)構(gòu)體,這些結(jié)構(gòu)體可以將原本意義屬于一個整體的數(shù)據(jù)組合在一起。從某種程度上來說,會不會用struct,怎樣用struct是區(qū)別一個開發(fā)人員是否具備豐富開發(fā)經(jīng)歷的標(biāo)志
在網(wǎng)絡(luò)協(xié)議、通信控制、嵌入式系統(tǒng)的C/C++編程中,我們經(jīng)常要傳送的不是簡單的字節(jié)流(char型數(shù)組),而是多種數(shù)據(jù)組合起來的一個整體,其表現(xiàn)形式是一個結(jié)構(gòu)體。
經(jīng)驗不足的開發(fā)人員往往將所有需要傳送的內(nèi)容依順序保存在char型數(shù)組中,通過指針偏移的方法傳送網(wǎng)絡(luò)報文等信息。這樣做編程復(fù)雜,易出錯,而且一旦控制方式及通信協(xié)議有所變化,程序就要進(jìn)行非常細(xì)致的修改。
一個有經(jīng)驗的開發(fā)者則靈活運用結(jié)構(gòu)體,舉一個例子,假設(shè)網(wǎng)絡(luò)或控制協(xié)議中需要傳送三種報文,其格式分別為packetA、packetB、packetC:
struct
?structA?

{
?
int
?a;
?
char
?b;
}
;

struct
?structB?

{
?
char
?a;
?
short
?b;
}
;

struct
?structC

{
?
int
?a;
?
char
?b;
?
float
?c;
}
?
優(yōu)秀的程序設(shè)計者這樣設(shè)計傳送的報文:
struct
?CommuPacket

{
?
int
?iPacketType;
//
報文類型標(biāo)志
?union
//
每次傳送的是三種報文中的一種,使用union
?
{
??
struct
?structA?packetA;
??
struct
?structB?packetB;
??
struct
?structC?packetC;
?}
}
;

在進(jìn)行報文傳送時,直接傳送struct CommuPacket一個整體。
假設(shè)發(fā)送函數(shù)的原形如下:
//
?pSendData:發(fā)送字節(jié)流的首地址,iLen:要發(fā)送的長度
Send(
char
?
*
?pSendData,?unsigned?
int
??iLen);
發(fā)送方可以直接進(jìn)行如下調(diào)用發(fā)送struct?CommuPacket的一個實例sendCommuPacket:
Send(?(
char
?
*
)
&
sendCommuPacket?,?
sizeof
(CommuPacket)?);
假設(shè)接收函數(shù)的原形如下:
//
?pRecvData:發(fā)送字節(jié)流的首地址,iLen:要接收的長度
//
返回值:實際接收到的字節(jié)數(shù)
unsigned?
int
?Recv(
char
?
*
?pRecvData,?unsigned?
int
??iLen);
接收方可以直接進(jìn)行如下調(diào)用將接收到的數(shù)據(jù)保存在struct CommuPacket的一個實例recvCommuPacket中:
Recv(?(
char
?
*
)
&
recvCommuPacket?,?
sizeof
(CommuPacket)?);

接著判斷報文類型進(jìn)行相應(yīng)處理:

switch
(recvCommuPacket.?iPacketType)

{
????
case
?PACKET_A:
????…????
//
A類報文處理
????
break
;
????
case
?PACKET_B:
????… ??
//
B類報文處理
????
break
;
????
case
?PACKET_C:
????…???
//
C類報文處理
????
break
;
}
以上程序中最值得注意的是
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );
中的強(qiáng)制類型轉(zhuǎn)換:(char *)&sendCommuPacket、(char *)&recvCommuPacket,先取地址,再轉(zhuǎn)化為char型指針,這樣就可以直接利用處理字節(jié)流的函數(shù)。
利用這種強(qiáng)制類型轉(zhuǎn)化,我們還可以方便程序的編寫,例如要對sendCommuPacket所處內(nèi)存初始化為0,可以這樣調(diào)用標(biāo)準(zhǔn)庫函數(shù)memset():
memset((char *)&sendCommuPacket,0, sizeof(CommuPacket));
2. struct的成員對齊
Intel、微軟等公司曾經(jīng)出過一道類似的面試題:
1
.?#include?
<
iostream.h
>
2
.?#pragma?pack(
8
)
3
.?
struct
?example1

4
.?
{
5
.?
short
?a;
6
.?
long
?b;
7
.?}
;

8
.?
struct
?example2

9
.?
{
10
.?
char
?c;
11
.?example1?struct1;
?
12
.?
short
?e;????
13
.?}
;
14
.?#pragma?pack()

15
.?
int
?main(
int
?argc,?
char
*
?argv[])

16
.?
{
?
17
.?example2?struct2;

18
.?cout?
<<
?
sizeof
(example1)?
<<
?endl;
19
.?cout?
<<
?
sizeof
(example2)?
<<
?endl;
20
.?cout?
<<
?(unsigned?
int
)(
&
struct2.struct1)?
-
?(unsigned?
int
)(
&
struct2)?
<<
?endl;

21
.?
return
?
0
;
22
.?}
問程序的輸入結(jié)果是什么?
答案是:
8
16
4
不明白?還是不明白?下面一一道來:
2.1 自然對界
struct是一種復(fù)合數(shù)據(jù)類型,其構(gòu)成元素既可以是基本數(shù)據(jù)類型(如int、long、float等)的變量,也可以是一些復(fù)合數(shù)據(jù)類型(如array、struct、union等)的數(shù)據(jù)單元。對于結(jié)構(gòu)體,編譯器會自動進(jìn)行成員變量的對齊,以提高運算效率。缺省情況下,編譯器為結(jié)構(gòu)體的每個成員按其自然對界(natural alignment)條件分配空間。各個成員按照它們被聲明的順序在內(nèi)存中順序存儲,第一個成員的地址和整個結(jié)構(gòu)的地址相同。
自然對界(natural alignment)即默認(rèn)對齊方式,是指按結(jié)構(gòu)體的成員中size最大的成員對齊。
例如:
struct naturalalign
{
?char a;
?short b;
?char c;
};
在上述結(jié)構(gòu)體中,size最大的是short,其長度為2字節(jié),因而結(jié)構(gòu)體中的char成員a、c都以2為單位對齊,sizeof(naturalalign)的結(jié)果等于6;
如果改為:
struct naturalalign
{
?char a;
?int b;
?char c;
};
其結(jié)果顯然為12。
2.2指定對界
一般地,可以通過下面的方法來改變?nèi)笔〉膶鐥l件:
· 使用偽指令#pragma pack (n),編譯器將按照n個字節(jié)對齊;
· 使用偽指令#pragma pack (),取消自定義字節(jié)對齊方式。
注意:如果#pragma pack (n)中指定的n大于結(jié)構(gòu)體中最大成員的size,則其不起作用,結(jié)構(gòu)體仍然按照size最大的成員進(jìn)行對界。
例如:
#pragma pack (n)
struct naturalalign
{
?char a;
?int b;
?char c;
};
#pragma pack ()
當(dāng)n為4、8、16時,其對齊方式均一樣,sizeof(naturalalign)的結(jié)果都等于12。而當(dāng)n為2時,其發(fā)揮了作用,使得sizeof(naturalalign)的結(jié)果為8。
在VC++ 6.0編譯器中,我們可以指定其對界方式(見圖1),其操作方式為依次選擇projetct > setting > C/C++菜單,在struct member alignment中指定你要的對界方式。
圖1:
在VC++ 6.0中指定對界方式
另外,通過__attribute((aligned (n)))也可以讓所作用的結(jié)構(gòu)體成員對齊在n字節(jié)邊界上,但是它較少被使用,因而不作詳細(xì)講解。
2.3 面試題的解答
至此,我們可以對Intel、微軟的面試題進(jìn)行全面的解答。
程序中第2行#pragma pack (8)雖然指定了對界為8,但是由于struct example1中的成員最大size為4(long變量size為4),故struct example1仍然按4字節(jié)對界,struct example1的size為8,即第18行的輸出結(jié)果;
struct example2中包含了struct example1,其本身包含的簡單數(shù)據(jù)成員的最大size為2(short變量e),但是因為其包含了struct example1,而struct example1中的最大成員size為4,struct example2也應(yīng)以4對界,#pragma pack (8)中指定的對界對struct example2也不起作用,故19行的輸出結(jié)果為16;
由于struct example2中的成員以4為單位對界,故其char變量c后應(yīng)補(bǔ)充3個空,其后才是成員struct1的內(nèi)存空間,20行的輸出結(jié)果為4。
3. C和C++間struct的深層區(qū)別
在C++語言中struct具有了“類” 的功能,其與關(guān)鍵字class的區(qū)別在于struct中成員變量和函數(shù)的默認(rèn)訪問權(quán)限為public,而class的為private。
例如,定義struct類和class類:
struct structA
{
char a;
…
}
class classB
{
????? char a;
????? …
}
則:
struct A a;
a.a = 'a';??? //訪問public成員,合法
classB b;
b.a = 'a';??? //訪問private成員,不合法
許多文獻(xiàn)寫到這里就認(rèn)為已經(jīng)給出了C++中struct和class的全部區(qū)別,實則不然,另外一點需要注意的是:
C++中的struct保持了對C中struct的全面兼容(這符合C++的初衷——“a better c”),因而,下面的操作是合法的:
//定義struct
struct structA
{
char a;
char b;
int c;
};
structA a = {'a' , 'a' ,1};??? //? 定義時直接賦初值
即struct可以在定義的時候直接以{ }對其成員變量賦初值,而class則不能,在經(jīng)典書目《thinking C++ 2nd edition》中作者對此點進(jìn)行了強(qiáng)調(diào)。
4. struct編程注意事項
看看下面的程序:
1. #include <iostream.h>
2. struct structA
3. {
4. int iMember;
5.?char *cMember;
6. };
7. int main(int argc, char* argv[])
8. {
9.?structA instant1,instant2;
10.char c = 'a';
???
11. instant1.iMember = 1;
12. instant1.cMember = &c;
?
13.instant2 = instant1;
?
14.cout << *(instant1.cMember) << endl;
?
15.*(instant2.cMember) = 'b';
?
16. cout << *(instant1.cMember) << endl;
?
17. return 0;
}
14行的輸出結(jié)果是:a
16行的輸出結(jié)果是:b
Why?我們在15行對instant2的修改改變了instant1中成員的值!
原因在于13行的instant2 = instant1賦值語句采用的是變量逐個拷貝,這使得instant1和instant2中的cMember指向了同一片內(nèi)存,因而對instant2的修改也是對instant1的修改。
在C語言中,當(dāng)結(jié)構(gòu)體中存在指針型成員時,一定要注意在采用賦值語句時是否將2個實例中的指針型成員指向了同一片內(nèi)存。
在C++語言中,當(dāng)結(jié)構(gòu)體中存在指針型成員時,我們需要重寫struct的拷貝構(gòu)造函數(shù)并進(jìn)行“=”操作符重載。
posted @
2006-05-05 17:26 季浩 閱讀(327) |
評論 (0) |
編輯 收藏