主頁 > 軟體工程 > 為什么重新打開stdout流后printf()作業例外

為什么重新打開stdout流后printf()作業例外

2022-01-28 16:28:32 軟體工程

重新打開 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(或stdinstderr)是未定義的行為。面對未定義的行為,奇怪的事情發生了。

從技術上講,無需多說。但是在我寫完這個答案之后,@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

標籤:

上一篇:Execvp命令不適用于“型別”命令

下一篇:LInux腳本洗掉匹配日期的行

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • Git本地庫既關聯GitHub又關聯Gitee

    創建代碼倉庫 使用gitee舉例(github和gitee差不多) 1.在gitee右上角點擊+,選擇新建倉庫 ? 2.選擇填寫倉庫資訊,然后進行創建 ? 3.服務端已經準備好了,本地開始作準備 (1)Git 全域設定 git config --global user.name "成鈺" git c ......

    uj5u.com 2020-09-10 05:04:14 more
  • CODING DevOps 代碼質量實戰系列第二課,相約周三

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。**《DevOps 代碼質量實戰(PHP 版)》**為 CODING DevOps 代碼質量實戰系列的第二課,同時也是本系列的 PHP ......

    uj5u.com 2020-09-10 05:07:43 more
  • 推薦Scrum書籍

    推薦Scrum書籍 直接上干貨,推薦書籍清單如下(推薦有順序的哦) Scrum指南 Scrum精髓 Scrum敏捷軟體開發 Scrum捷徑 硝煙中的Scrum和XP : 我們如何實施Scrum 敏捷軟體開發:Scrum實戰指南 Scrum要素 大規模Scrum:大規模敏捷組織的設計 用戶故事地圖 用 ......

    uj5u.com 2020-09-10 05:07:45 more
  • CODING DevOps 代碼質量實戰系列最后一課,周四發車

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。 **《DevOps 代碼質量實戰(Java 版)》**為 CODING DevOps 代碼質量實戰系列的最后一課,同時也是本系列的 ......

    uj5u.com 2020-09-10 05:07:52 more
  • 敏捷軟體工程實踐書籍

    Scrum轉型想要做好,第一步先了解并真正落實Scrum,那么我推薦的Scrum書籍是要看懂并實踐的。第二步是團隊的工程實踐要做扎實。 下面推薦工程實踐書單: 重構:改善既有代碼的設計 決議極限編程 : 擁抱變化 代碼整潔代碼 程式員的職業素養 修改代碼的藝術 撰寫可讀代碼的藝術 測驗驅動開發 : ......

    uj5u.com 2020-09-10 05:07:55 more
  • Jenkins+svn+nginx實作windows環境自動部署vue前端專案

    前面文章介紹了Jenkins+svn+tomcat實作自動化部署,現在終于有空抽時間出來寫下Jenkins+svn+nginx實作自動部署vue前端專案。 jenkins的安裝和配置已經在前面文章進行介紹,下面介紹實作vue前端專案需要進行的哪些額外的步驟。 注意:在安裝jenkins和nginx的 ......

    uj5u.com 2020-09-10 05:08:49 more
  • CODING DevOps 微服務專案實戰系列第一課,明天等你

    CODING DevOps 微服務專案實戰系列第一課**《DevOps 微服務專案實戰:DevOps 初體驗》**將由 CODING DevOps 開發工程師 王寬老師 向大家介紹 DevOps 的基本理念,并探討為什么現代開發活動需要 DevOps,同時將以 eShopOnContainers 項 ......

    uj5u.com 2020-09-10 05:09:14 more
  • CODING DevOps 微服務專案實戰系列第二課來啦!

    近年來,工程專案的結構越來越復雜,需要接入合適的持續集成流水線形式,才能滿足更多變的需求,那么如何優雅地使用 CI 能力提升生產效率呢?CODING DevOps 微服務專案實戰系列第二課 《DevOps 微服務專案實戰:CI 進階用法》 將由 CODING DevOps 全堆疊工程師 何晨哲老師 向 ......

    uj5u.com 2020-09-10 05:09:33 more
  • CODING DevOps 微服務專案實戰系列最后一課,周四開講!

    隨著軟體工程越來越復雜化,如何在 Kubernetes 集群進行灰度發布成為了生產部署的”必修課“,而如何實作安全可控、自動化的灰度發布也成為了持續部署重點關注的問題。CODING DevOps 微服務專案實戰系列最后一課:**《DevOps 微服務專案實戰:基于 Nginx-ingress 的自動 ......

    uj5u.com 2020-09-10 05:10:00 more
  • CODING 儀表盤功能正式推出,實作作業資料可視化!

    CODING 儀表盤功能現已正式推出!該功能旨在用一張張統計卡片的形式,統計并展示使用 CODING 中所產生的資料。這意味著無需額外的設定,就可以收集歸納寶貴的作業資料并予之量化分析。這些海量的資料皆會以圖表或串列的方式躍然紙上,方便團隊成員隨時查看各專案的進度、狀態和指標,云端協作迎來真正意義上 ......

    uj5u.com 2020-09-10 05:11:01 more
最新发布
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:41:12 more
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:35:34 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:05:44 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:00:18 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:20:31 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:55 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:18:51 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:00 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:17:55 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:12:06 more