重新打開 STDOUT 流后,如果像這樣呼叫 print(),則訊息不會顯示在我的螢屏上:
printf("The message disappeared\n")
用于解釋問題的代碼段:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
printf("Display a message\n");
int fd, fd_copy, new_fd;
FILE *old_stream = stdout;
fd = STDOUT_FILENO;
fd_copy = dup(fd);
fclose(old_stream);
new_fd = dup2(fd_copy, fd);
close(fd_copy);
FILE *new_stream = fdopen(fd, "w");
stdout = new_stream;
printf("test %d\n", 1);
fprintf(stdout, "test 2\n");
int rc = printf("test 3\n");
printf("Test 4 Why the message disappeared\n");
printf("Error message is [%s]\n", strerror(errno));
return 0;
}
為什么只有測驗 4不能顯示在我的螢屏上。他們不都使用標準輸出作為輸出嗎?
輸出:
# gcc main.c; ./a.out
Display a message
test 1
test 2
test 3
Error message is [Bad file descriptor]
上面的代碼片段來自 LVM2 庫函式。
int reopen_standard_stream(FILE **stream, const char *mode)
/* https://github.com/lvmteam/lvm2/blob/master/lib/log/log.c */
我設計的動態庫:
我包裝了一個動態庫,它包含 LVM 動態庫以供其他行程使用。其中一個功能是(輸出系統中的所有 PV):
char global_buffer[0x1000];
void show_pvs_clbk_fn(int level, const char *file, int line,
int dm_errno, const char *format)
{
/* Extract and process output here rather than printing it */
if (level != LVM2_LOG_PRINT)
return;
sprintf(global_buffer, "%s%s\n", global_buffer, format)
}
int show_all_PV(char *out_buffer)
{
void *handle = lvm2_init();
lvm2_log_fn(show_pvs_clbk_fn);
int rc = lvm2_run(handle, "pvs");
lvm2_exit(handle);
if (rc != LVM2_COMMAND_SUCCEEDED) {
return -1;
}
strcpy(out_buffer, global_buffer)
return 0;
}
呼叫者可以像這樣呼叫 show_all_PV() API:
int main(void)
{
char tmp[0x1000];
if (!show_all_PV(tmp)) {
printf("====== PVS are ======\n");
printf("%s\n", tmp);
}
}
輸出:
====== PVS are ======
PV VG Fmt Attr PSize PFree
/dev/nvme1n1p1 vg1 lvm2 a-- <1.2t 1.1t
一些呼叫者可能會弄亂標準輸出:
我發現一個奇怪的事情是,如果呼叫者定義了一個包含vfprintf (stdout, ) 系統呼叫的函式。他們永遠不會從普通的 print() API 獲得輸出。
#inclide <stdlin.h>
#inclide <stdio.h>
#inclide <unistd.h>
#inclide <stdarg.h>
#if 1
int a_function_never_be_called(const char *formatP, ...)
{
va_list ap;
va_start(ap, formatP);
vfprintf(stdout, formatP, ap);
va_end(ap);
return 0;
}
#endif
int main(void)
{
char tmp[0x1000];
if (!show_all_PV(tmp)) {
printf("====== PVS are ======\n");
printf("%s\n", tmp);
}
}
The string "====== PVS are ======" disappeared and the caller got an IO error Bad file descripto.
Output:
PV VG Fmt Attr PSize PFree
/dev/nvme1n1p1 vg1 lvm2 a-- <1.2t 1.1t
uj5u.com熱心網友回復:
分配給stdout(或stdin或stderr)是未定義的行為。面對未定義的行為,奇怪的事情發生了。
從技術上講,無需多說。但是在我寫完這個答案之后,@zwol在評論中指出glibc 檔案聲稱允許重新分配標準 IO 流。在這些方面,這種行為是一個錯誤。我接受這個事實,但 OP 并不是基于 glibc 的使用,還有許多其他標準庫實作沒有做出這種保證。在其中一些中,分配給stdout會在編譯時引發錯誤;在其他情況下,它根本無法作業或無法始終如一地作業。換句話說,不管 glibc 是什么,分配給的stdout是未定義的行為,而試圖這樣做的軟體充其量是不可移植的。(而且,正如我們所見,即使在 glibc 上,它也會導致不可預測的輸出。)
但是我的好奇心被激發了,所以我調查了一下。首先是查看 gcc 生成的實際代碼,并查看每個輸出呼叫實際呼叫的庫函式:
printf("test %d\n", 1); /* Calls printf("test %d\n", 1); */
fprintf(stdout, "test 2\n"); /* Calls fwrite("test 2\n", 1, 7, stdout); */
int rc = printf("test 3\n"); /* Calls printf("test 3\n"); */
printf("Test 4 Why the message disappeared\n");
/* Calls puts("Test 4...disappeared"); */
printf("Error message is [%s]\n", strerror(errno));
/* Calls printf("..."); */
請注意,GCC 正在努力優化呼叫。在第 2 行和第 4 行中,它能夠找到非 printf 庫呼叫,從而避免了格式字串的運行時決議。
但請注意,在第 3 行的情況下它不會這樣做,它看起來與第 4 行相同。為什么不呢?因為您使用的是 的回傳值printf,即發送到標準輸出的字符數。但這與 的回傳值不同puts,它只是在成功時回傳一個“非負數”。所以替代是不可能的。
假設我們int rc = 從第 3 行洗掉,然后重新編譯。現在我們得到這個:
printf("test %d\n", 1); /* Calls printf("test %d\n", 1); */
fprintf(stdout, "test 2\n"); /* Calls fwrite("test 2\n", 1, 7, stdout); */
printf("test 3\n"); /* Calls puts("test 3"); */
printf("Test 4 Why the message disappeared\n");
/* Calls puts("Test 4...disappeared"); */
printf("Error message is [%s]\n", strerror(errno));
/* Calls printf("..."); */
因此,在不使用回傳值的情況下,GCC 可以將 printf 替換為 puts。(另請注意,當它進行替換時,它還會\n從字串文字中洗掉 ,因為它會puts自動在其輸出的末尾添加一個換行符。)
當我們運行修改后的程式時,我們會看到:
Display a message
test 1
test 2
Error message is [Bad file descriptor]
現在,有兩行消失了,這正是 GCC 使用的兩行puts.
經過一開始的惡作劇,puts不再起作用,大概是因為它依賴于stdout沒有被重新分配。允許這樣做,因為重新分配stdout是未定義的行為。freopen(如果你想重新打開,你可以使用stdout。)
最后說明:
Unsurprisingly, it turns out that the glibc team did accept it as a bug; it was reported as bug 24051 and a similar issue with stdin as bug 24153. Both were fixed in glibc v2.30, released in August of 2019. So if you have a recently upgraded Linux install, or you are reading this answer years after I wrote it, you might not see this bug.
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/422266.html
標籤:
下一篇:LInux腳本洗掉匹配日期的行
