跳轉到內容

C 程式設計/stdio.h/函式參考

來自華夏公益教科書,開放的書籍,開放的世界


void clearerr( FILE *stream );

clearerr 函式重置給定流的錯誤標誌和 EOF 指示器。當發生錯誤時,您可以使用 perror() 來確定實際發生的錯誤。

fclose 是 C 語言中屬於 ANSI C 標準庫的函式,包含在 stdio.h 檔案中。它的目的是關閉流及其所有相關結構。通常流是一個開啟的檔案。fclose 有以下原型

int fclose(FILE *file_pointer)

它接受一個引數:指向要關閉的流的 FILE 結構的指標,例如::fclose(my_file_pointer) 此行呼叫 fclose 函式以關閉 my_file_pointer 指向的 FILE 流結構。

返回值是一個整數,具有以下含義

  • 0(零):流已成功關閉;
  • EOF:發生錯誤;

可以透過讀取 errno 來檢查錯誤。如果 fclose 嘗試關閉當前未分配給檔案的 檔案指標,則其行為未定義 - 在許多情況下,會導致程式崩潰。

它通常是關閉系統呼叫的包裝器。

示例用法

[編輯 | 編輯原始碼]
 #include <stdio.h>

 int main(void) 
 {
   FILE *file_pointer;
   int i;
 
   file_pointer = fopen("myfile.txt", "r");
   fscanf(file_pointer, "%d", &i);
   printf("The integer is %d\n", i);
   fclose(file_pointer);
   
   return 0;
 }

上面的程式開啟一個名為 myfile.txt 的檔案,並掃描其中的整數。

feof 是 C 標準庫函式,在 stdio.h 標頭檔案中宣告。[1] 它的主要目的是區分流操作已到達檔案末尾的情況和EOF(“檔案末尾”)錯誤程式碼作為通用錯誤指示器返回的情況,而實際上並未到達檔案末尾。

函式原型

[編輯 | 編輯原始碼]

函式宣告如下

int feof(FILE *fp);

它接受一個引數:指向要檢查的流的FILE 結構的指標。

返回值

[編輯 | 編輯原始碼]

函式的返回值是一個整數。非零值表示已到達檔案末尾;值為零表示尚未到達檔案末尾。

示例程式碼

[編輯 | 編輯原始碼]
 #include <stdio.h>
 
 int main()
 {
     FILE *fp = fopen("file.txt", "r");
     int c;
     c = getc(fp);
     while (c != EOF) {
         /* Echo the file to stdout */
         putchar(c);
         c = getc(fp);
     }
     if (feof(fp))
       puts("End of file was reached.");
     else if (ferror(fp))
       puts("There was an error reading from the stream.");
     else
       /*NOTREACHED*/
       puts("getc() failed in a non-conforming way.");
 
     fclose(fp);
     return 0;
 }

對該函式的一種常見誤用是嘗試使用feof“搶先”。但是,這不能正常工作,因為feof僅在讀取函式失敗後才為描述符設定。

int fgetc(FILE *stream)

C 標準庫 stdio.h 中的 fgetc 函式用於從檔案流中讀取一個字元。如果成功,fgetc 將返回檔案中當前位置處的字元。如果失敗,fgetc 將返回錯誤程式碼。對於文字檔案(與二進位制檔案相對),值 25 和 26(替換字元和跳脫字元)的字元可能會使輸入流在之後無法使用,而是返回單個最大值。

錯誤程式碼

-1:引數錯誤:不是整數
-2:引數錯誤:超出範圍
-3:檔案未開啟

fgetpos 是 C 標準庫 I/O 部分中的一個函式。

int fgetpos(FILE *stream, fpos_t *pos);

fgetpos()將從 stdio 流中檢索檔案偏移量(以位元組為單位)stream到型別儲存fpos t由變數指向pos,應由呼叫者分配。此變數僅應用於後續呼叫fsetpos()。一個單獨的函式,ftell()可以被用作替代,以檢索檔案偏移量的值作為一個長整數。

成功完成時返回值為 0,出錯時返回值為 -1(在這種情況下errno將相應設定)。

fgets 是 C 程式語言中的一個函式,它從給定的檔案流源中讀取有限數量的字元到字元陣列中。[2] fgets 代表 file get string。它包含在 C 標準庫標頭檔案 stdio.h 中。函式的原型如下

char* fgets(char *string, int length, FILE * stream);

該函式在找到換行符或到達檔案末尾後結束讀取,或者在讀取 (length - 1) 個字元後結束讀取。如果遇到換行符,它將作為最後一個字元包含在字串中,緊接在空字元之前。長度引數包含空字元所需的空間,該空字元將附加到字串的末尾。因此,要讀取 N 個字元,長度規範必須指定為 N+1。如果至少讀取了一個字元且未發生錯誤,則返回讀取的字串,否則返回 NULL 指標。

stream 引數指定要從其讀取字串的流。stdin 通常用於此,用於從標準輸入讀取。否則,使用fopen() 函式返回的 FILE * 值。

示例用法

[編輯 | 編輯原始碼]

以下程式碼從控制檯輸入讀取字元,並使用 puts 函式以每行 20 個字元的方式打印出來,直到出現 EOF 為止。

#include <stdio.h>

#define MAX_LEN 20

int main(void)
{
  char str_buf[MAX_LEN + 1]; // One extra byte needed
                             // for the null character

  while(fgets(str_buf, sizeof str_buf, stdin) != NULL)
	puts(str_buf);

  return 0;
}

在 POSIX 工具中的使用

[編輯 | 編輯原始碼]

為了符合 POSIX 工具的行長要求,通常使用 LINE_MAX(通常在 limits.h 中找到)來定義字元緩衝區的大小。

fgets() 強制使用固定大小的緩衝區在需要處理任意長文字行的應用程式中很麻煩。

符合 POSIX.1-2008 標準的系統提供了一個名為 getline() 的函式(最初是 GNU 擴充套件[3]),它將讀取整行,如果緩衝區不夠長,則重新分配緩衝區。[4]

高階應用程式可以使用 mmap 來避免緩衝區限制。

在 glibc 和 musl 中實現的 fopen 將使用 open 系統呼叫。

使用 fopen 開啟檔案

[編輯 | 編輯原始碼]

使用 fopen 開啟檔案,它返回一個與指定檔案或其他裝置關聯的 I/O 流,可以從該檔案或裝置讀取和寫入。如果函式失敗,它將返回一個空指標。

相關的 C 庫函式 freopen 在首先關閉與其引數關聯的任何開啟的流之後執行相同的操作。

它們的定義如下

FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *fp);

fopen 函式本質上是 Unix 作業系統 open 系統呼叫的稍微更高級別的包裝器。同樣,fclose 通常是 Unix 系統呼叫 close 的薄包裝器,而 C FILE 結構本身通常對應於 Unix 檔案描述符。在 POSIX 環境中,fdopen 函式可用於從檔案描述符初始化 FILE 結構;但是,檔案描述符是純粹的 Unix 概念,標準 C 中不存在。

fopenfreopenmode 引數必須是一個以以下序列之一開頭的字串

模式 描述 開始..
r rb 以讀模式開啟(檔案必須存在) 開頭
w wb 以寫模式開啟(如果檔案不存在則建立檔案)。刪除內容並覆蓋檔案。 開頭
a ab 以追加模式開啟(如果檔案不存在則建立檔案) 結尾
r+ rb+ r+b 以讀寫模式開啟(檔案必須存在) 開頭
w+ wb+ w+b 以讀寫模式開啟。如果檔案存在則刪除內容並覆蓋檔案,否則建立一個空的新檔案 開頭
a+ ab+ a+b 以讀寫模式開啟(如果檔案存在則追加) 結尾

b”代表二進位制。C 標準規定了兩種型別的檔案——文字檔案和二進位制檔案——儘管作業系統不需要區分這兩種型別。文字檔案是一個由文字組成的檔案,文字按行排列,並使用某種區分的換行符或序列(在 Unix 中,一個簡單的換行符;在 Microsoft Windows 中,回車符後跟一個換行符)。當從文字檔案中讀取位元組時,換行符序列通常對映為換行符,以便於處理。當寫入文字檔案時,簡單的換行符在寫入之前對映為特定於作業系統的換行符序列。二進位制檔案是一個檔案,其中位元組以“原始”方式讀取,並以“原始”方式傳遞,沒有任何型別的對映。

當以更新模式開啟檔案時('+'作為模式引數中的第二個或第三個字元),可以在關聯的流上執行輸入和輸出。但是,寫入之後不能緊跟著讀取,除非在中間呼叫 fflush 或檔案定位函式(fseekfsetposrewind),讀取之後也不能緊跟著寫入,除非在中間呼叫檔案定位函式。[5]

寫入和追加模式將嘗試建立給定名稱的檔案,如果不存在這樣的檔案。如上所述,如果此操作失敗,fopen 將返回 NULL

Glibc/musl 說明

[編輯 | 編輯原始碼]

Musl 和 Glibc 支援e在 Linux 上的模式,它設定O_CLOEXEC在新的檔案描述符上。O_CLOEXEC是 Linux 2.6 的特性,在 Linux 手冊頁專案 中有記載

為新的檔案描述符啟用 close-on-exec 標誌。指定此標誌允許程式避免額外的 fcntl(2) F_SETFD 操作來設定 FD_CLOEXEC 標誌。此外,在某些多執行緒程式中使用此標誌至關重要,因為使用單獨的 fcntl(2) F_SETFD 操作來設定 FD_CLOEXEC 標誌不足以避免一個執行緒開啟檔案描述符而另一個執行緒同時執行 fork(2) 加 execve(2) 時的競爭條件。

參見 http://danwalsh.livejournal.com/53603.html

參見 musl cgit 以瞭解 Musl 的實現。

fputs 是 C 程式語言中的一個函式,它將一個字元陣列寫入給定的檔案流。fputs 代表 file put string。它包含在 C 標準庫標頭檔案 stdio.h 中。

函式 fputs 在遇到終止空字元 ('\0') 後結束。空字元不會複製到流中。函式的原型如下

int fputs ( const char * str, FILE * stream );

stream 引數指定要寫入字串的流。這裡通常使用 stdout,用於寫入標準輸出。否則,將使用 fopen() 函式返回的 FILE * 值。

示例用法

[編輯 | 編輯原始碼]

以下示例是使用 fputs 的“hello world”程式

#include <stdio.h>

int main()  {
    const char *buffer = "Hello world!";
    fputs (buffer, stdout);
    return 0;
    }

以下是詢問使用者姓名並將其寫出的程式

#include <stdio.h>
int main() {
    char name[50];
    fputs("What is your name? ", stdout);
    fgets(name, sizeof(name), stdin);
    fputs(name, stdout);
    return 0;
    }

fread 是一個從檔案讀取緩衝的二進位制輸入的函式。[6] 它包含在標準 C 庫中的 stdio.h 標頭檔案中。

size_t fread (void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream)

fread 函式將 nmemb 個大小為 size 的資料項從名為 stream 的輸入流複製到 ptr 指向的陣列中。一個數據項是一個長度為 size 的位元組序列(不一定是空位元組終止的)。fread 在讀取了 nmemb 個專案、到達檔案末尾或發生錯誤時停止追加位元組。返回時,fread 將設定流中的檔案指標,指向已讀取的最後一個位元組的下一個位元組。stream 的內容保持不變。fread 函式返回實際讀取的專案數。如果 nmemb 為零,則不執行任何操作,函式將返回 0。

該函式可能出現以下錯誤程式碼

  • EAGAIN - 在不阻塞程序的情況下無法立即讀取輸入流,並且為與流相關的檔案描述符設定了 O_NONBLOCK 標誌。
  • EBADF - 不是用於讀取的有效檔案描述符。
  • EINTR - 讀取操作在讀取任何資料之前被訊號終止。
  • EIO - 無法從控制終端讀取。當程序位於後臺程序組中,並且程序嘗試從其控制終端讀取失敗時,就會發生這種情況,要麼是因為程序組已成為孤兒,要麼是因為程序正在忽略或阻塞 SIGTTIN 訊號。
  • ENOMEM - 可用儲存空間不足。
  • ENXIO - 嘗試從不存在的裝置或能力不足的裝置讀取。

fseek 是一個屬於 ANSI C 標準庫的 C 函式,包含在檔案 stdio.h 中。它的目的是更改指定流的檔案位置指示器。由於 fseek 在許多平臺上使用 32 位值,因此它具有最大 2 GB 搜尋的限制。fseeko64 將用於更大的偏移量。

int fseek(FILE *stream_pointer, long offset, int origin);

fseek 函式將與流關聯的檔案指標移動到從 origin 偏移位元組的新位置。

引數含義

  • stream_pointer 是指向流 FILE 結構的指標,其位置指示器應更改;
  • offset 是一個 long 整數,指定應放置位置指示器的位置從 origin 的位元組數;
  • origin 是一個整數,指定起始位置。它可以是
    • SEEK_SET:origin 是流的開頭
    • SEEK_CUR:origin 是當前位置
    • SEEK_END:origin 是流的末尾

返回值

[編輯 | 編輯原始碼]

返回值是一個 integer,表示

  • 0(零) :函式在流中成功執行
  • 非零 :發生錯誤
  • 在無法進行搜尋的裝置上,返回值未定義。

請注意,每個錯誤號都有不同的含義。可以透過檢查 errno.h 來揭示含義。

#include <stdio.h>

int main(int argc, char **argv) {
  FILE *file_handle;
  long int file_length;
  
  file_handle = fopen("file.bin","rb");
  if(fseek(file_handle, 0, SEEK_END)) {
    puts("Error while seeking to end of file");
    return 1;
  }
  file_length = ftell(file_handle);
  if (file_length < 0) {
    puts("Error while reading file position");
    return 2;
  }
  
  printf("File length: %d bytes\n", file_length);
  fclose(file_handle);
  return 0;
}

此程式程式碼以只讀模式開啟名為 file.bin 的檔案。檔案長度透過搜尋到末尾,然後讀迴文件指標的位置來確定。

fsetpos 將流定位在 *ptr 中由 fgetpos 記錄的位置。fsetpos 在出錯時返回非零值。它是檔案定位函式之一。

int fsetpos(file *stream, const fpos_t *ptr)

函式 ftell 返回流中相對於第一個位元組的當前偏移量。它返回流的當前檔案位置,如果發生錯誤,則返回 -1。

 long ftell ( FILE * stream );

fwprintf是 C 標準庫函式,如 wchar.h 中所定義。所需的標頭是 whar.h 和 stdio.h。它是 fprintf 的寬字元版本。它的函式簽名是

int fwprintf(FILE *fp, const wchar_t *format, argument list);

在 fwprintf 的簽名中,fp 是要傳送輸出的流。format 是寬字元流,指定輸出的格式。格式字串負責您需要提供的其他引數。

fwprintf 將輸出放在指定的輸出流上。fwprintf 對流執行寬字元輸出,該流不是位元組導向的。fwprintf 是將引數列表中指定的所有引數根據格式中寬字元格式說明符進行轉換、列印和格式化的函式。fwprintf() 函式在 format 指向的寬字串的控制下將輸出寫入流。

fwprintf() 函式可以轉換、列印和格式化括號中的引數,這些引數將轉換格式寬字元字串。這裡格式由 編號引數說明(如 "%n$" 和 "*m$")或 非編號引數轉換說明符(如 % 和 *)組成。但兩者不能同時存在。

返回值

[編輯 | 編輯原始碼]

fwprintf 返回使用的寬字元數,不包括 NULL 字元。如果發生錯誤,則返回負數。

freadfwrite 函式分別提供檔案輸入和輸出操作。freadfwrite<stdio.h> 中宣告。它通常包裝 write 系統呼叫。

使用 fwrite 寫入檔案

[編輯 | 編輯原始碼]

fwrite 定義為

int fwrite ( const void * array, size_t size, size_t count, FILE * stream );

fwrite 函式將一塊資料寫入流。它將向流中的當前位置寫入 count 個元素的陣列。對於每個元素,它將寫入 size 個位元組。流的位置指示器將根據成功寫入的位元組數前進。

該函式將返回成功寫入的元素數。如果寫入成功完成,則返回值將等於 count。如果出現寫入錯誤,則返回值將小於 count

以下程式開啟一個名為sample.txt的檔案,向檔案寫入一個字元陣列,然後關閉它。

 #include <stdio.h>
 
 int main(void)
 {
     FILE *file_ptr;
     int iCount;
     char arr[6] = "hello";
 
     file_ptr = fopen("sample.txt", "wb");
     iCount = fwrite(arr, 1, 5, file_ptr);
     fclose(file_ptr);
 
     return 0;
 }

getc 是字元輸入函式之一。getc 從檔案讀取下一個字元,它接收指向檔案的 file 指標。它是讀取檔案的 simplest 函式。

與 getchar 一樣,getc() 可以實現為宏而不是函式。getc 等效於 fgetc。getc 從 fp 指向的流中返回下一個字元;它返回 EOF 表示檔案結束或錯誤。

int getc( FILE * stream);

這裡,引數 stream 是指向 FILE 物件的指標,該物件標識要執行操作的流。

/*getc 示例*/ <Source lang="c">

  1. include <stdio.h>

int main() { FILE *fp; int c; int n = 0; fp = fopen("myfile.txt", "r");

 if(fp == NULL)
    perror ("Error opening file");
 else
 {
    do  {
        c = getc(fp);
        if(c == '#')
        n++;
        }
    while(c != EOF);
 fclose(fp);
 printf ("File contains %d#.\n",n);
 }
return 0;
}

</syntaxhighlight>

上面的程式逐個字元地讀取名為 myfile.txt 的檔案,並使用 n 變數來統計檔案中包含的 '#' 字元數量。

返回值

[編輯 | 編輯原始碼]

讀取的字元將作為 int 值返回。

如果到達檔案末尾或發生讀取錯誤,函式將返回 EOF 和相應的錯誤指示器。我們可以使用 ferror 或 feof 來確定是否發生了錯誤或到達了檔案末尾。

[編輯 | 編輯原始碼]

POSIX 錯誤函式 perror 用於 C 和 C++ 中,根據儲存在 errno 中的錯誤狀態,將錯誤訊息列印到 stderr。它列印 str 和一個與全域性變數 errno 對應的實現定義的錯誤訊息。[7]

perror 的定義最初是在 System V 介面定義的第 1 版中釋出的。

#include <stdio.h>
void perror(const char* prefix);
int fd = open("/etc/passwd", O_RDONLY);
if (fd == -1) {
    perror("open");
    exit(1); 
}

如果引數 prefix 為非 NULL,perror 首先會將 prefix 列印到標準錯誤,後面跟著一個冒號和一個空格。然後,它會將 strerror 的結果列印到標準錯誤,後面跟著一個換行符。例如,上面的示例可能會列印

open: Permission denied
printf 函式的示例。

Printf 函式(代表“print formatted”)是一類通常與某些型別的程式語言相關的函式。它們接受一個名為格式字串的字串引數,該引數指定了將任意數量的各種資料型別引數(s)呈現為字串的方法。然後,預設情況下,此字串將列印到標準輸出流,但存在執行其他任務的變體。格式字串中的字元通常按字面意義複製到函式的輸出中,其他引數在由格式說明符標記的位置呈現為結果文字,格式說明符通常由%字元引入。

時間線

[編輯 | 編輯原始碼]

許多程式語言都實現了printf函式,用於輸出格式化的字串。它起源於 C 程式語言,它具有類似於以下原型的原型

int printf(const char *format, ...)

字串常量format提供對輸出的描述,帶由“%”跳脫字元標記的佔位符,用於指定函式應該產生的輸出的相對位置和型別。返回值產生列印的字元數。

Fortran、COBOL

[編輯 | 編輯原始碼]

Fortran 的可變引數PRINT語句引用了一個不可執行的FORMAT語句。

      PRINT 601, 123456, 1000.0, 3.1415, 250
  601 FORMAT (8H RED NUM I7,4H EXP,E8.1, ' REAL' F5.2,'; VALUE=',I4)

將列印以下內容(在前進到新行後,因為如果定向到列印裝置,則由於前導空格字元)[8]

 RED NUM 123456 EXP 1.0E 03 REAL 3.14; VALUE= 250

COBOL 透過分層資料結構規範提供格式化

 01 out-rec.
   02 out-name   picture x(20).
   02 out-amount picture  $9,999.99.

...

    move me to out-name.
    move amount to out-amount.
    write out-rec.

1960 年代:BCPL、ALGOL 68、Multics PL/I

[編輯 | 編輯原始碼]

C 的可變引數printf起源於 BCPL 的writef函式。

ALGOL 68 草案和最終報告具有函式infoutf,後來這些函式從原始語言中刪除並替換為現在更熟悉的readf/getfprintf/putf

printf(($"Color "g", number1 "6d,", number2 "4zd,", hex "16r2d,", float "-d.2d,", unsigned value"-3d"."l$,
            "red", 123456, 89, BIN 255, 3.14, 250));

Multics 具有一個名為ioa_的標準函式,它具有各種各樣的控制程式碼。它基於 Multics 的 BOS(引導作業系統)中的機器語言功能。

 call ioa_ ("Hello, ^a", "World!");

1970 年代:C、Lisp

[編輯 | 編輯原始碼]
 printf("Color %s, number1 %d, number2 %05d, hex %x, float %5.2f, unsigned value %u.\n",
        "red", 123456, 89, 255, 3.14159, 250);

將列印以下行(包括換行符,\n)

Color red, number1 123456, number2 00089, hex ff, float  3.14, unsigned value 250.

printf函式返回列印的字元數,或者如果發生輸出錯誤,則返回負值。

Common Lisp 具有format函式。

 (format t "Hello, ~a" "World!")

在標準輸出流上列印"Hello, World!"。如果第一個引數為nil,則 format 會將其字串返回給呼叫者。第一個引數也可以是任何輸出流。format 於 1978 年在麻省理工學院的 ZetaLisp 中引入,基於 Multics 的ioa_,後來被採用到 Common Lisp 標準中。

1980 年代:Perl、Shell

[編輯 | 編輯原始碼]

Perl 也具有printf函式。Common Lisp 具有一個 format 函式,它根據與printf相同的原則起作用,但使用不同的字元進行輸出轉換。GLib 庫包含g_print,它是printf的實現。

一些 Unix 系統有一個printf程式,用於在 shell 指令碼中使用。這可以在 echo 不可移植的情況下使用。例如

echo -n -e "$FOO\t$BAR"

可以以可移植的方式重寫為

printf "%s\t%s" "$FOO" "$BAR"

1990 年代:PHP、Python

[編輯 | 編輯原始碼]

1991:Python 的%運算子在內插元組內容時,類似於printf的語法。例如,此運算子可與print函式一起使用

print("%s\t%s" % (foo,bar))

Python 的 2.6 版本包含了str.format(),它比已過時的%更可取,後者可能會在未來版本的 Python 中消失

print("If you multiply five and six you get {0}.".format(5*6))

1995:PHP 也具有printf函式,其規範和用法與 C/C++ 中的相同。MATLAB 沒有printf,但有其兩個擴充套件sprintffprintf,它們使用相同的格式字串。sprintf返回一個格式化的字串,而不是產生視覺化輸出。

2000 年代:Java

[編輯 | 編輯原始碼]

2004:從 1.5 版開始,Java 支援printf作為PrintStream[9]類的成員,賦予它printf和 fprintf 函式的功能。同時,透過新增format(String, Object... args)方法,將類似sprintf的功能新增到String類中。[10]

// Write "Hello, World!" to standard output (like printf)
System.out.printf("%s, %s", "Hello", "World!"); 
// create a String object with the value "Hello, World!" (like sprintf)
String myString = String.format("%s, %s", "Hello", "World!");

與大多數其他實現不同,Java 的printf實現會在遇到格式錯誤的格式字串時丟擲異常。

[編輯 | 編輯原始碼]

ANSI C 標準規定了 printf 的多種變體,用於輸出流不是預設流、引數列表形式不同、輸出目標是記憶體而不是檔案描述符等情況。 printf 函式本身通常只是一個包含預設值的包裝器,它包裝了這些變體之一。

int fprintf(FILE *stream, const char *format, ...)

fprintf 使 printf 輸出能夠寫入任何檔案。 程式設計師經常使用它來列印錯誤資訊,透過寫入標準錯誤裝置,但它可以與使用 fopen(或 fdopen)函式開啟的任何檔案一起使用。

int sprintf (char *str, const char *format, ...)

sprintf 列印到字串(char 陣列)而不是標準輸出。 sprintf 的使用者必須透過計算或透過保護頁來確保結果字串不會大於為 str 分配的記憶體。 無法確保這一點會導致緩衝區溢位。

在 PHP 等高階語言中,sprintf 函式沒有 str 引數。 相反,它返回格式化的輸出字串。 PHP 中的原型如下

string sprintf (const string format, ...)

緩衝區安全性和 sprintf

[編輯 | 編輯原始碼]

在 ISO C99 中,引入了 snprintf 作為 sprintf 的替代方案,可以幫助避免緩衝區溢位的風險。

int snprintf(char *str, size_t size, const char * restrict format, ...)

snprintf 保證不會向 str 中寫入超過 size 位元組的資料,因此使用它可以幫助避免緩衝區溢位的風險,如下面的程式碼片段所示

#define BUFFER_SIZE 50
char buf[BUFFER_SIZE];
int n;
n = snprintf(buf, BUFFER_SIZE, "Your name is %s.\n", username);
if (n < 0 || n >= BUFFER_SIZE)
   /* Handle error */

如果上面的示例中 username 導致結果長度超過 49 位元組,該函式將透過截斷最終位元組來限制儲存到 buf 中的字串。 空終止符將始終寫入第 50 個位置,因此結果始終以空終止。 此外,snprintf 的返回值指示該函式如果存在足夠的空間會向字串寫入多少位元組(不包括空字元)。 系統可以使用此資訊在需要整個字串時分配一個新的(更大的)緩衝區。

許多 snprintf 實現偏離了上述描述,特別是許多 Windows 庫、2.0.6 之前的 glibc 版本和 Solaris。 最常見的錯誤是在截斷時返回 -1 而不是所需的長度。 更麻煩的是,有些實現沒有在截斷時寫入空終止符,或者返回 size-1(無法檢測到截斷)。 這些偏差使使用 snprintf 編寫可移植的安全程式碼比應有的難度更大。

另一個安全的 sprintf 替代方案是 asprintf,它是一個 GNU 擴充套件。

int asprintf(char **ret, const char *format, ...)

asprintf 自動分配足夠的空間來容納最終字串。 它將 *ret 設定為指向結果字串的指標,如果發生錯誤則設定為未定義的值(glibc 值得注意的是,它是在錯誤時不會始終將 *ret 設定為 NULL 的唯一實現)。 使用 asprintf 的程式設計師有責任在使用後釋放分配的記憶體。 雖然不是任何標準的一部分,但 asprintf 出現在多個作業系統的 C 庫中(包括 OpenBSD、FreeBSD 和 NetBSD),以及 libiberty 庫的其他平臺上。

GLib 提供了另一個安全的替代方案:g_strdup_printf,它分配了足夠的記憶體,但與 asprintf 不同,它將結果字串作為返回值返回,而不是透過第一個引數。

C++ 中用於數字轉換的 sprintf 替代方案

[編輯 | 編輯原始碼]

C++ 中用於字串格式化和其他型別轉換為字串的標準方法是 iostream。 與 printf 不同,iostream 標準庫是型別安全的且可擴充套件的。

一個常見的程式設計任務是將數字型別轉換為字串(char 緩衝區)。 sprintf 家族雖然有用,但對於如此簡單的任務來說可能過於複雜。 此外,許多使用它們的程式並非旨在處理當 區域設定 改變時輸出的差異。

在 C/C++ 中開發了許多替代方法。

vprintf、vfprintf、vsprintf、vsnprintf 和 vasprintf

[編輯 | 編輯原始碼]
#include <stdio.h>
/* va_list versions of above */
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
int vasprintf(char **ret, const char *format, va_list ap);

這些函式類似於上面沒有 v 的函式,不同之處在於它們使用可變引數列表。 這些函式為程式設計師提供了建立自己的 printf 變體的能力。 例如,程式設計師可以編寫一個函式

void fatal_error(const char *format, ...)

該函式將使用 va_start 宏從額外引數中獲取 va_list 變數,使用 vfprintf 在標準錯誤裝置上列印訊息,使用 va_end 宏清理 va_list 變數,最後執行必要的任務來乾淨地關閉程式。

這些函式的另一個常見應用是編寫一個自定義 printf,它列印到與檔案不同的目標。 例如,一個圖形庫可能提供一個類似 printf 的函式,它包含 X 和 Y 座標

int graphical_printf(int x, int y, const char *format, ...)

這將透過使用 vsnprintfvasprintf 將字串臨時儲存到私有緩衝區來實現。

格式佔位符

[編輯 | 編輯原始碼]

格式化是透過格式字串中的佔位符進行的。 例如,如果一個程式想要打印出一個人的年齡,它可以先加上 "你的年齡是 " 的字首來顯示輸出。 為了表示我們想要在該訊息之後立即顯示年齡的整數,我們可以使用格式字串

"Your age is %d."

格式佔位符的語法為

%[引數][標誌][寬度][.精度][長度]型別
  • 引數 可以省略或可以為
字元 描述
n$ n 是使用此格式說明符顯示的引數的編號,允許提供的引數使用不同的格式說明符或以不同的順序多次輸出。 這是一個 POSIX 擴充套件,不在 C99 中。 示例:printf("%2$d %2$#x; %1$d %1$#x",16,17) 生成

"17 0x11; 16 0x10"

  • 標誌 可以是以下之一或多個(按任意順序)
字元 描述
-
(減號)
左對齊此佔位符的輸出(預設是右對齊輸出)。
+
(加號)
在正號數字型別之前新增一個加號。 正數 = '+',負數 = '-'。 (預設情況下,正數前面不新增任何內容)。
 
(空格)
在正號數字型別之前新增一個空格。 正數 = ' ',負數 = '-'。 如果存在 '+' 標誌,則忽略此標誌。 (預設情況下,正數前面不新增任何內容)。
0
(零)
當指定 寬度 選項時,在數字前面新增零。 (預設情況下,在數字前面新增空格)。

示例:printf("%2d", 3) 生成 " 3",而 printf("%02d", 3) 生成 "03"。

#
(井號)
備用形式。 對於 'g' 和 'G',不會刪除尾隨零。 對於 'f'、'F'、'e'、'E'、'g'、'G',輸出始終包含一個小數點。 對於 'o'、'x'、'X' 或 '0',分別在非零數字之前新增 '0x'、'0X'。
  • 寬度 指定要輸出的最小字元數,通常用於填充製表輸出中的固定寬度欄位,否則欄位會更小,儘管它不會導致截斷過大的欄位。 寬度值中的前導零被解釋為上面提到的零填充標誌,負值被視為與上面提到的左對齊 "-" 標誌結合使用的正值。
  • 精度通常指定輸出的最大限制,具體取決於特定格式型別。對於浮點數型別,它指定輸出應四捨五入的小數點右邊的位數。對於字串型別,它限制應輸出的字元數,超過該限制後字串將被截斷。
  • 長度可以省略,也可以是以下任一值:
字元 描述
hh 對於整數型別,導致printf預期一個由char提升的int大小的整數引數。
h 對於整數型別,導致printf預期一個由short提升的int大小的整數引數。
l 對於整數型別,導致printf預期一個long大小的整數引數。
ll 對於整數型別,導致printf預期一個long long大小的整數引數。
L 對於浮點數型別,導致printf預期一個long double引數。
z 對於整數型別,導致printf預期一個size_t大小的整數引數。
j 對於整數型別,導致printf預期一個intmax_t大小的整數引數。
t 對於整數型別,導致printf預期一個ptrdiff_t大小的整數引數。

此外,在廣泛使用 ISO C99 擴充套件之前,存在一些特定於平臺的長度選項。

字元 描述
I 對於有符號整數型別,導致printf預期ptrdiff_t大小的整數引數;對於無符號整數型別,導致printf預期size_t大小的整數引數。在 Win32/Win64 平臺上很常見。
I32 對於整數型別,導致printf預期一個 32 位(雙字)整數引數。在 Win32/Win64 平臺上很常見。
I64 對於整數型別,導致printf預期一個 64 位(四字)整數引數。在 Win32/Win64 平臺上很常見。
q 對於整數型別,導致printf預期一個 64 位(四字)整數引數。在 BSD 平臺上很常見。

ISO C99 包含inttypes.h標頭檔案,其中包含許多用於平臺無關printf編碼的宏。示例宏包括

字元 描述
PRId32 通常等效於I32dWin32/Win64)或d
PRId64 通常等效於I64dWin32/Win64)、lld32 位平臺)或ld64 位平臺
PRIi32 通常等效於I32iWin32/Win64)或i
PRIi64 通常等效於I64iWin32/Win64)、lli32 位平臺)或li64 位平臺
PRIu32 通常等效於I32uWin32/Win64)或u
PRIu64 通常等效於I64uWin32/Win64)、llu32 位平臺)或lu64 位平臺
PRIx64 通常等效於I64xWin32/Win64)、llx32 位平臺)或lx64 位平臺
  • 型別可以是以下任一值:
字元 描述
di int 作為有符號十進位制數。'%d' 和 '%i' 在輸出時是同義詞,但在用於scanf()進行輸入時是不同的。
u 列印十進位制unsigned int
fF double 以普通(定點)表示法。'f' 和 'F' 僅在列印無窮大數或 NaN 的字串方式上有所不同('f' 為 'inf'、'infinity' 和 'nan','F' 為 'INF'、'INFINITY' 和 'NAN')。
eE double 值以標準形式([-]d.ddd e[+/-]ddd)表示。E 轉換使用字母 E(而不是 e)來引入指數。指數始終包含至少兩位數字;如果值為零,則指數為 00。在 Windows 中,指數預設包含三位數字,例如 1.5e002,但可以使用 Microsoft 特定的_set_output_format 函式進行更改。
gG double 以普通或指數表示法表示,以哪種方式更適合其大小而定。'g' 使用小寫字母,'G' 使用大寫字母。這種型別與定點表示法略有不同,因為不會包含小數點右邊的無意義零。此外,整數不會包含小數點。
xX unsigned int 作為十六進位制數。'x' 使用小寫字母,'X' 使用大寫字母。
o unsigned int 以八進位制形式表示。
s 以 null 結尾的字串。
c char(字元)。
p void *(指向 void 的指標)以實現定義的格式表示。
n 不列印任何內容,但將迄今為止成功寫入的字元數寫入一個整數指標引數。
% 一個字面百分號字元(此型別不接受任何標誌、寬度、精度或長度)。

寬度和精度格式引數可以省略,也可以是嵌入在格式字串中的固定數字,或者在格式字串中用星號 "*" 表示時作為另一個函式引數傳遞。例如,printf("%*d", 5, 10) 將導致列印"   10",總寬度為 5 個字元,而printf("%.*s", 3, "abcdef") 將導致列印 "abc"。

如果轉換規範的語法無效,則行為未定義,並可能導致程式終止。如果提供的函式引數過少,無法為模板字串中的所有轉換規範提供值,或者如果引數型別不正確,則結果也將未定義。多餘的引數將被忽略。在許多情況下,未定義的行為會導致“格式字串攻擊”安全漏洞。

一些編譯器,例如 GNU 編譯器集合,將靜態檢查類似 printf 的函式的格式字串並警告出現的問題(當使用標誌-Wall-Wformat 時)。如果將非標準“format” __attribute__ 應用於函式,GCC 也會警告使用者定義的類似 printf 的函式。

在表格輸出中使用欄位寬度與顯式分隔符的風險

[edit | edit source]

僅使用欄位寬度來提供製表,例如使用格式 "%8d%8d%8d" 來表示三個 8 字元列中的三個整數,不能保證如果資料中出現大數字,欄位分隔符將被保留。欄位分隔符的丟失很容易導致輸出損壞。在鼓勵將程式作為指令碼中的構建塊使用的系統中,這種損壞的資料通常會轉發並損壞進一步的處理,無論原始程式設計師是否期望輸出僅供人眼閱讀。透過在所有表格輸出格式中包含顯式分隔符,即使是空格,也可以消除此類問題。只需將前面的危險示例更改為 " %7d %7d %7d" 就可以解決此問題,在數字變大之前格式相同,但隨後由於顯式包含的空格而明確防止它們在輸出時合併。類似的策略也適用於字串資料。

自定義格式佔位符

[edit | edit source]

有一些printf類函式的實現允許擴充套件基於跳脫字元的迷你語言,從而允許程式設計師為非內建型別使用特定的格式化函式。最著名的一種是(現已棄用)的 glibc register_printf_function()。但是,由於它與靜態格式字串檢查衝突,因此很少使用它。另一種是Vstr 自定義格式化程式,它允許新增多字元格式名稱,並且可以與靜態格式檢查器一起使用。

一些應用程式(例如 Apache HTTP 伺服器)包含自己的printf類函式,並將擴充套件嵌入其中。但是,這些函式往往與register_printf_function()存在相同的問題。

大多數具有printf類函式的非 C 語言透過僅使用 "%s" 格式並將物件轉換為字串表示來解決此功能的缺乏問題。C++ 提供了一個顯著的例外,因為它繼承了 C 的歷史,因此具有printf函式,但它也具有完全不同的機制,而該機制是首選的。

putcstdio.h中的函式。它是開啟檔案後寫入檔案的最簡單方法。它將字元寫入流並前進位置指示器。它是輸出函式。字元將寫入流的當前位置,如內部位置指示器所示,然後位置指示器前進一個字元。

putc 等效於fputc,也需要一個流作為引數,但putc 可能被實現為宏,因此傳遞的引數不應是可能產生副作用的表示式。

有關沒有流引數的類似函式,請參見putchar

int putc ( int character, FILE * stream );

引數

[edit | edit source]
字元
要寫入的字元。字元以其 int 提升形式傳遞。
stream
指向 FILE 物件的指標,該物件標識要寫入字元的流。

返回值

[edit | edit source]

如果沒有錯誤,將返回與寫入的字元相同的字元。如果發生錯誤,將返回 EOF 並且錯誤指示器將被設定。


示例

[edit | edit source]

/* putc 示例:字母寫入器 */ <Source lang="c">

  1. include <stdio.h>

int main () {

 FILE *fp;
 char c;
 fp = fopen("alphabet.txt", "wt");
 for (c = 'A' ; c <= 'Z' ; c++)
   {
    putc (c , fp);
   }
 fclose (fp);
 return 0;

} </syntaxhighlight>

輸出

[edit | edit source]

此示例程式建立一個名為 alphabet.txt 的檔案,並將 ABCDEFGHIJKLMNOPQRSTUVWXYZ 寫入其中。

putchar 是 C 程式語言中用於將單個字元寫入標準輸出流 stdout 的函式。[11] 它的原型如下

int putchar (int character)

要列印的字元作為引數輸入到函式中,如果寫入成功,則返回引數字元。否則,返回檔案結尾。

putchar 函式在 C 標準庫標頭檔案 stdio.h 中定義。

示例用法

[編輯 | 編輯原始碼]

以下程式使用 getchar 將字元讀入陣列,並在找到檔案結尾字元後使用 putchar 函式將其打印出來。

 #include <stdio.h>

 int main(void)
 {
   char str[1000];
   int ch, i, n = 0;
  
   while ((ch = getchar()) != EOF && n < 1000)
     str[n++] = ch;
	   
   for (i = 0; i < n; ++i)
     putchar(str[i]);

   putchar('\n'); /* trailing '\n' needed in Standard C */
	
   return 0;
 }

程式將讀取長度的最大值指定為 1000 個字元。它將在讀取 1000 個字元或讀取檔案結尾指示符後停止讀取,以先發生者為準。

puts 是一個用於輸出字串(加上一個換行符)的函式,例如

#include <stdio.h>
int main() {
    puts("welcome to WIKIPEDIA!!!");
}

輸出(在 stdout 上)

welcome to WIKIPEDIA!!!

與 printf 的區別

1. puts 在提供的文字後列印一個換行符

2. puts 按原樣列印字串(它不會處理 % 程式碼)。

我們也可以將一個變數傳遞給 puts,例如

#include <stdio.h>
int main() {
    const char *str = "welcome to WIKIPEDIA!!!";
    puts(str);
}

輸出

welcome to WIKIPEDIA!!!

puts 具有以下原型

int puts(const char *str)

它將列印 str 中的每個位元組,直到遇到空字元,然後列印一個換行符。puts 返回寫入的位元組數(包括換行符),或 EOF(如果發生錯誤)。

要列印一個字串而不處理 % 程式碼,或輸出換行符,請嘗試

    printf("%s", "welcome to WIKIPEDIA!!!");

remove 是 C 程式語言中用於刪除特定檔案的函式。它包含在 C 標準庫標頭檔案 stdio.h 中。

函式的原型如下

int remove ( const char * filename );

如果成功,函式返回零。如果失敗,則返回非零值,並且 errno 變數設定為相應的錯誤程式碼。

示例用法

[編輯 | 編輯原始碼]

以下程式演示了 remove 的常見用法

#include <stdio.h>

int main() 
{
    const char *filename = "a.txt";
    remove (filename);
    return 0;
}

要實現一個簡單的刪除工具

#include <stdio.h>

int main(char* argv[], int argc) 
{
    if (argc > 1) {
        return remove (argv[1]);
    }else{
        return -1;
    }
}

rename 是 C 程式語言中用於重新命名特定檔案的函式。[12]

函式的原型是

int rename(const char *oldname, const char *newname)

成功時返回零。如果失敗,則返回其他整數,在這種情況下,errno 被設定為錯誤程式碼。

rename 函式在 C 的 stdio.h 庫標頭檔案和 C++ 的 cstdio 標頭檔案中定義。它在 ANSI C 中定義。

在 POSIX 中,如果舊名稱和新名稱位於不同的掛載檔案系統上,則 rename 將失敗(返回 EXDEV)。[13] 如果 rename 呼叫成功,則保證它從當前主機(即另一個程式)的角度來看是原子的(即,另一個程式只會看到帶有舊名稱的檔案或帶有新名稱的檔案,而不是兩者都有或兩者都沒有)。

Windows C 庫版本的 rename 如果目標檔案已存在則失敗(POSIX 只有在使用者沒有刪除目標檔案的許可權時才會失敗)。雖然底層的 WIN32 呼叫(從 Win2K 開始)有一個選項可以複製 POSIX 原子行為,但庫從未被修復為使用它。

rewind 是 C 程式語言中的一個函式,它在 stdio.h 庫標頭檔案中定義。該函式將檔案位置指示器移動到指定流的開頭,同時還清除與該流相關的錯誤和 EOF 標誌。[14]

rewind 的作用與為傳遞的流呼叫 fseek(stream, 0L, SEEK_SET) 相同,只是 rewind 會導致錯誤指示器也被清除。

#include <stdio.h>
void rewind( FILE *stream );

scanf 是一個函式,它從給定的字串流源讀取具有指定格式的資料,起源於 C 程式語言,並且存在於許多其他程式語言中。

scanf 函式的原型是

int scanf(const char *format, ...);

該函式返回成功匹配的專案總數,該總數可能小於請求的總數。如果輸入流耗盡或在任何專案匹配之前從輸入流讀取失敗,則返回 EOF。

據可追溯的範圍,“scanf”代表“scan format”,因為它掃描輸入以查詢有效標記並根據指定的格式解析它們。

scanf 函式在 C 中找到,它從標準輸入(通常是命令列介面或類似型別的文字使用者介面)讀取數字和其他資料型別的輸入。

以下顯示了 C 中的程式碼,它從標準輸入讀取可變數量的無格式十進位制整數,並在單獨的行上打印出每個整數

#include <stdio.h>

int
main(void)
{
    int n;
    while (scanf("%d", & n))
        printf("%d\n", n);
    return 0;
}

在被上述程式處理後,一個雜亂無章的整數列表,例如

456 123 789     456 12
456 1
      2378

將整齊地顯示為

456
123
789
456
12
456
1
2378

要打印出一個單詞

#include <stdio.h>

int
main(void)
{
    char word[20];
    if(scanf("%19s", word) == 1)
        puts(word);
    return 0;
}

無論程式設計師希望程式讀取什麼資料型別,引數(如上面的 &n)都必須是指向記憶體的指標。否則,函式將無法正常執行,因為它將嘗試覆蓋錯誤的記憶體部分,而不是指向您試圖獲取輸入的變數的記憶體位置。

由於 scanf 被指定為僅從標準輸入讀取,因此許多具有介面的程式語言,如 PHP,具有 sscanffscanf 等派生函式,而不是 scanf 本身。

派生函式

[編輯 | 編輯原始碼]

根據輸入的實際來源,程式設計師可以使用 scanf 的不同派生函式。兩個常見的例子是 sscanffscanf

fscanf 派生函式從指定的流檔案讀取輸入。原型如下

(C 或 C++)

int fscanf (FILE *file, const char *format, ...);

(PHP)

mixed fscanf (resource file, const string format [, mixed args...])

fscanf 派生函式的工作方式與原始 scanf 函式相同 - 讀取的輸入部分將不會再次讀取,直到檔案關閉並重新開啟。

sscanf 派生函式從作為第一個引數傳遞的字元字串讀取輸入。與 fscanf 的一個重要區別是,每次呼叫函式時,都會從頭開始讀取字串。沒有在成功讀取操作後遞增的“指標”。原型如下

(C 或 C++)

int sscanf (const char *str, const char *format, ...);

(PHP)

mixed sscanf (const string str, const string format [, mixed args...])

vscanf、vsscanf 和 vfscanf

[編輯 | 編輯原始碼]
int vscanf (const char *format, va_list args);
int vsscanf (const char *source, const char *format, va_list args);
int vfscanf (FILE *file, const char *format, va_list args);

它們與沒有 v 字首的相同函式類似,只是它們從 va_list 獲取引數。(參見 stdarg.h。)這些變體可用於可變引數函式。

格式字串規範

[編輯 | 編輯原始碼]

scanf 中的格式佔位符與它的逆函式 printf 中的格式佔位符大致相同。

格式字串中很少有常量(即不是格式佔位符的字元),主要是因為程式通常不是為了讀取已知資料而設計的。例外情況是一個或多個空格字元,這些字元會丟棄輸入中的所有空格字元。

以下是一些最常用的佔位符:

  • %d : 以帶符號十進位制數的形式掃描整數。
  • %i : 以帶符號數的形式掃描整數。類似於 %d,但在以 0x 開頭時將數字解釋為十六進位制,以 0 開頭時解釋為八進位制。例如,字串 031 使用 %d 讀取為 31,使用 %i 讀取為 25。標誌 h%hi 中表示轉換為 shorthh 表示轉換為 char
  • %u : 掃描十進位制 unsigned int(注意,在 C99 標準中,輸入值的減號是可選的,因此如果讀取負[需要澄清] 數,不會出現錯誤,結果將是二進位制補碼。參見 strtoul()Template:Failed verification) 類似地,%hu 掃描 unsigned short%hhu 掃描 unsigned char
  • %f : 以普通(定點)表示法掃描浮點數。
  • %g, %G : 以普通或指數表示法掃描浮點數。%g 使用小寫字母,%G 使用大寫字母。
  • %x, %X : 以無符號十六進位制數的形式掃描整數。
  • %o : 以八進位制數的形式掃描整數。
  • %s : 掃描字元字串。掃描在空格處終止。在字串末尾儲存一個空字元,這意味著提供的緩衝區必須至少比指定的輸入長度長一個字元。
  • %c : 掃描一個字元(char)。不會新增空字元。
  • (space): 空格掃描空格字元。
  • %lf : 以雙精度浮點數形式掃描。
  • %Lf : 以長雙精度浮點數形式掃描。

以上可以在數字修飾符和 lL 修飾符(代表“long”)之間組合使用,這些修飾符位於百分號和字母之間。百分號和字母之間也可以有數字值,在 long 修飾符(如果有)之前,這些數字值指定要掃描的字元數。百分號後的可選星號 (*) 表示由該格式說明符讀取的資料不儲存在變數中。格式字串後面的引數不應包含此丟棄的變數。

printf 中的 ff 修飾符在 scanf 中不存在,導致輸入和輸出模式之間的差異。llhh 修飾符在 C90 標準中不存在,但在 C99 標準中存在。[15]

格式字串的示例如下:

"%7d%s %c%lf"

上面的格式字串將前七個字元掃描為十進位制整數,然後讀取剩餘字元為字串,直到遇到空格、換行符或製表符,然後掃描後面的第一個非空格字元和一個雙精度浮點數。

錯誤處理

[編輯 | 編輯原始碼]

scanf 通常用於程式無法保證輸入是否符合預期格式的情況。因此,健壯的程式必須檢查 scanf 呼叫是否成功,並採取適當的措施。如果輸入格式不正確,錯誤資料仍將保留在輸入流中,必須讀取並丟棄這些資料,然後才能讀取新的輸入。另一種避免這種情況的輸入讀取方法是使用 fgets,然後檢查讀取的字串。例如,最後一步可以透過 sscanf 完成。

printf 一樣,scanf 也容易受到格式字串攻擊。應格外小心,確保格式字串包含字串和陣列大小的限制。在大多數情況下,來自使用者的輸入字串大小是任意的;在 scanf 函式執行之前無法確定它。這意味著沒有長度說明符的 %s 佔位符的使用本質上是不安全的,並且可以用於緩衝區溢位。另一個潛在問題是允許動態格式字串,例如儲存在配置檔案或其他使用者控制檔案中的格式字串。在這種情況下,除非事先檢查格式字串並強制實施限制,否則無法指定字串大小的允許輸入長度。與之相關的是額外的或不匹配的格式佔位符,這些佔位符與實際的 vararg 列表不匹配。這些佔位符可能會從堆疊中部分提取,根據 varargs 的特定實現,可能包含不希望的甚至不安全的指標。

/* 另一個僅在某些特定編譯器上有效的用法是

scanf("請輸入一個值 %d",&n);

這會列印引號中的字串,並在指示的 % 號處停止接受輸入。*/

setvbuf 是標準 C 中的一個函式,它允許程式設計師控制檔案流的緩衝。它在 <stdio.h> 中宣告;其函式原型為

int setvbuf(FILE *stream, char *buf, int mode, size_t size);

stream 引數是指向檔案流的指標,相關緩衝操作將在此檔案流上執行;buf 是長度為 size 的字元陣列,或為 NULL 指標;mode 是所需的緩衝型別:_IOFBF,表示完全緩衝,_IOLBF 表示行緩衝,_IONBF 表示不緩衝。這三個宏在 <stdio.h> 中定義。setvbuf 在成功時返回零,在失敗時返回非零值。

如果 buf 為 NULL 指標,系統將動態分配一個指定大小的緩衝區(size 個字元)。如果 mode_IONBF,流 I/O 將不會被緩衝,導致流上的每個後續 I/O 操作立即執行,並且 bufsize 引數將被忽略。

一個相關的函式 setbuf 也控制檔案流的緩衝。與 setvbuf 不同,setbuf 僅接受兩個引數。原型為

void setbuf(FILE *stream, char *buf);

setbuf 的行為等同於

(void)setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);

也就是說,如果 buf 不為 NULL,則使用給定的緩衝區將流設定為完全緩衝;否則,將流設定為不緩衝。如果向 setbuf 提供緩衝區,它必須至少 BUFSIZ 位元組長。該函式始終成功。

下面的程式碼非常不穩定,可能無法在特定編譯器上正常工作。它甚至可能發生緩衝區溢位。C99 規定,在寫入流後不能呼叫 setvbuf,因此此程式碼呼叫未定義的行為。C99 腳註 230(非規範性)表示,在 main 結束時,應該關閉流,然後再釋放 buf。

該程式的輸出應為 Hello world 後跟一個換行符。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char buf[42];

    if(setvbuf(stdout, buf, _IOFBF, sizeof buf)) {
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }
    printf("He");
    /* The buffer contains "He"; nothing is written yet to stdout */
    fflush(stdout); /* "He" is actually written to stdout */

    if(setvbuf(stdout, NULL, _IONBF, 0)) {
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }
    printf("llo w"); /* "llo w" is written to stdout, there is no buffering */

    if(setvbuf(stdout, buf, _IOLBF, sizeof buf)) {
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }
    printf("orld"); /* The buffer now contains "orld"; nothing is written yet to stdout */
    putchar('\n'); /* stdout is line buffered; everything in the buffer is now written to stdout along with the newline */

    return EXIT_SUCCESS;
}

在計算中,tmpfile 是一個 ISO C/POSIX 函式,用於建立臨時檔案,臨時檔案是當開啟檔案的程式關閉或終止時不再存在的計算機檔案。[16][17][18]

C

#include <stdio.h>

C++

#include <cstdio>

FILE* tmpfile(void);

函式 tmpfile 在成功時報告指向有效檔案流的指標;在失敗時,它返回 NULL[16]

錯誤條件

[編輯 | 編輯原始碼]

除了返回 NULL 外,tmpfile 在失敗時還會設定 errno。如果 tmpfile 失敗,errno 的允許值如下:[16]

  • EINTR - 如果在執行 tmpfile 期間捕獲到訊號。
  • EMFILE - 如果已達到檔案描述符的最大數量和/或檔案流的最大數量(在程序中)。
  • ENFILE - 如果當前開啟的檔案數量已達到系統允許的最大數量。
  • ENOSPC - 如果檔案系統中沒有空間來建立臨時檔案。
  • EOVERFLOW - 如果檔案是普通檔案,並且檔案的大小無法在 off_t 型別的物件中正確表示。
  • ENOMEM - 如果沒有足夠的記憶體來分配檔案流。

注意事項

[編輯 | 編輯原始碼]

tmpfile 函式容易受到多種安全漏洞的攻擊;使用非可移植的 mkstemp(UNIX)或 tmpfile_s(MSVCRT)函式來代替,以避免這些問題。[19][20]

Microsoft C 執行時庫中該函式的實現試圖在當前驅動器的根目錄中建立檔案,通常會失敗並報告“拒絕訪問”。

Ungetc 是 C 標準庫函式 ungetch 的一個版本。它在某種程度上是一個受限的函式。

int ungetc (int c, FILE *stream)

這裡,c 是一個字元變數,stream 是一個檔案指標,整個函式用整數資料型別表示。

如上所述,它是一個 ungetch 的受限版本,該函式將 c 值指定的字元壓入堆疊。在壓入時,字元 c 被轉換為 unsigned char,然後壓入輸入 stream。如果對流應用 getc 函式,則可以返回壓入的字元。

呼叫容量

[編輯 | 編輯原始碼]

一次呼叫只允許壓入一個字元。嘗試連續壓入字元可能正常工作,也可能不正常工作。在正常和常用的實踐中,該函式被呼叫 4 次,因此連續壓入了 4 個字元。這意味著該函式的處理過程完全取決於機器。因此,如果機器中的記憶體範圍很大,則有可能進行無限次的壓入操作。

返回值

[編輯 | 編輯原始碼]

壓入的字元將由流上的任何後續讀取操作返回(按相反順序),如果流的輸入被緩衝,則意味著最後壓入的字元將首先返回。當正常壓入一個字元時,該函式將返回最後壓入的字元。如果壓入操作未正確完成,這意味著如果壓入字元不成功(例如,如果檔案未開啟),則返回 EOF 字元作為錯誤。無法使用 ungetc 將 EOF 字元壓入流。成功呼叫 ungetc 函式將清除流的 EOF 指示符。

要擦除從呼叫 ungetc 函式得到的壓入字元,在從流中讀取字元之前,需要呼叫 fseekfsetposfflush 函式。由於所有壓入的字元都已讀取,因此檔案位置指示器與壓入字元之前的狀態相同。

Ungetcungetch 的受限版本

[編輯 | 編輯原始碼]

如前所述,它是一個函式 ungetch 的受限版本,它具有相同的限制,例如,當讀取操作緊隨寫入操作或反之亦然時。這就是為什麼在 ungetc 和隨後的 writeread 函式之間需要一箇中間重新定位操作。

vwprintf是一個 C 標準庫函式,如 wchar.h 中所定義。它的函式簽名如下

int vwprintf(const wchar t *format, va_list args);

在函式 vwprintf 的簽名中,format 是格式專門化,args 是指向引數的指標。

與其他函式的比較

[編輯 | 編輯原始碼]

vwprintf 的功能與 swprintf 相同。這兩個函式之間的區別在於 引數列表已被指向引數列表的指標替換。vwprintf 將寬字元輸出到類似 stdout 的字串,而 stdout 不應該是面向位元組的。

vwprintf 的功能與 swprintf 相同。這兩個函式之間的區別在於 引數列表已被指向引數列表的指標替換。vwprintf 將寬字元輸出到類似 stdout 的字串,而 stdout 不應該是面向位元組的。此函式返回的字元數不包括空字元,如果發生輸出錯誤,則返回任何負值。vwprintf() 等效於帶有引數列表被 arg 替換的 wprintf,arg 可以透過 vastart 宏初始化。

返回值

[編輯 | 編輯原始碼]

vwprintf 在成功時返回寫入的字元數,但不包括 NULL 字元。但在失敗時,它會返回錯誤,並設定 errno。

wprintf是一個 C 標準庫函式,如 wchar.h 中所定義。它的函式簽名如下

int wprintf(const wchar t *format,...);

wprintf() 將輸出寫入 stdout(標準輸出流)。它使用可變引數列表。該函式以及 vprintf、vfprintf、vsprintf、vsnprintf 和 vasprintf 等函式為程式設計師提供了建立自己的 printf 變體的功能。

wprintf 函式在 C 中可以找到,它在格式字串的控制下將輸出寫入流。格式字串指定後續引數如何轉換為輸出。

wprintf 是 printf 格式的寬字元版本。格式是一個寬字元字串,其中 wprintf 和 printf 在它們以 ANSI 模式開啟時行為類似。

以下是用於理解 wprintf 工作原理的示例程式碼。

程式碼

#include<stdio.h>
#include<wchar.h>
int main(int argc,char *argv[])
{
      wchar_t *str = L"@TAJMAHAL@#$$$";
      wprintf(L"%ls\n", str);
      return EXIT_SUCCESS;
}

執行程式碼後,輸出將如下所示

@TAJMAHAL@#$$$

限制
1. wprintf() 不是可移植函式。
2. wprintf() 不能與 printf() 混用。
3. wprintf 無法列印雙精度值。

  for e.g. 2^56 is the double value which cannot be printed using wprintf().

wscanf

wscanf 是 C 程式語言中 C 標準庫函式。
它轉換 寬字元 輸入。該函式由標頭檔案 wchar.h 支援。wscanf 是 scanf 的寬字元版本。


語法
int wscanf(const wchar_t[21]*input 以格式化的形式)
它返回格式正確的輸入數量。如果存在格式錯誤的寬字元輸入,則計數可能為零或小於輸入的數量。


示例 <Source lang="c">

  1. include<stdio.h>

int main() {

  int   j, result;
  float a;
  char  ch, string[128];
  wchar_t wch, wst[128];
  result = scanf( "%d %f %c %C %80s %80S", &j, &a, &ch, &wch, string, wst );
  printf( "The number of fields input is %d\n", result );
  printf( "The contents are: %d %f %c %C %s %S\n", j, a, ch, wch, string, wst);
  result = wscanf( L"%d %f %hc %lc %80S %80ls", &j, &a, &ch, &wch, string, wst );
  wprintf( L"The number of fields input is %d\n", result );
  wprintf( L"The contents are: %d %f %C %c %hs %s\n", j, a, ch, wch, string, wst);
  return 0;

}</syntaxhighlight>


現在,如果給定的輸入如下所示:83 56.6 k m Scanf input
54 22.3 a f Wscanf input


那麼輸出將如下所示
輸入欄位數量為 6
內容為:83 56.599998 k m Scanf input
輸入欄位數量為 6
內容為:54 22.300003 a f Wscanf input
因此,scanf 返回格式正確或成功分配的欄位數量。它不會返回格式錯誤的欄位的計數,並且這些值被讀取但未分配。如果在檔案第一次讀取字元時遇到檔案結尾字元,則返回EOF

  1. ISO/IEC 9899:1999 規範 (PDF). 第 305 頁,第 7.19.10.2 節。
  2. ISO/IEC 9899:1999 規範 (PDF). 第 296 頁,第 7.19.7.2 節。
  3. http://www.gnu.org/software/libc/manual/html_node/Line-Input.html#index-getline-993
  4. http://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html
  5. http://www.opengroup.org/onlinepubs/009695399/functions/fopen.html
  6. ISO/IEC 9899:1999 規範 (PDF). 第 301 頁,第 7.19.8.1 節。
  7. ISO/IEC 9899:1999 規範 (PDF). 第 305 頁,第 7.19.10.2 節。
  8. "ASA 列印控制字元". 檢索於 2010 年 2 月 12 日.
  9. "PrintStream (Java 2 Platform SE 5.0)". Sun Microsystems Inc. 1994. 檢索於 2008-11-18.
  10. "String (Java 2 Platform SE 5.0)". Sun Microsystems Inc. 1994. 檢索於 2008-11-18.
  11. ISO/IEC 9899:1999 規範 (PDF). 第 299 頁,第 7.19.7.9 節。
  12. ISO/IEC 9899:1999 規範 (PDF). 第 268 頁,第 7.19.4.2 節。
  13. rename: 重新命名檔案 - 系統介面參考,The Single UNIX® Specification, Issue 7 from The Open Group
  14. [1], The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008.
  15. C99 標準,第 7.19.6.2 節 “fscanf 函式” 第 11 段。
  16. a b c tmpfile 由 OpenGroup
  17. 臨時檔案 由 GNU C 庫
  18. tmpfile 由 HMUG
  19. TMPNAM-TMPFILE 漏洞 由 Build Security In
  20. VOID FI039-C. 安全建立臨時檔案 由 CERT
  21. wchar_t
華夏公益教科書