所以我一直在練習使用 CDECL 和 STDCALL 呼叫約定在 FASMW 中撰寫簡單的子例程,這讓我想知道 C 中的 printf 函式將使用什么。
此外,在 x86 32 位匯編中定義函式會很棒。如果這不是太多的要求。
uj5u.com熱心網友回復:
根據 C 2018 7.1.4 2 和 7.21.6.3 1,printf如果程式將其宣告為int printf(const char * restrict format, ...);,則必須作業,因此printf必須使用 C 實作的默認呼叫約定。
一個 C 實作可能會提供 的多個實作printf,因此可以#include <stdio.h>提供一個printf函式或宏的替代宣告,以指定 的第二個實作printf。但是,必須提供上述主要實作。
此外,在 x86 32 位匯編中定義函式會很棒。
printf除非通過編譯 C 和/或其他語言的實作獲得,否則您不太可能找到用匯編語言實作的高質量現代實作。實際上,您不太可能在單個例程中找到定義;printf是一個包含許多子部分的復雜例程,通常其實作分散在多個例程和源檔案中。這個答案和這個答案有一些實作的鏈接。前者包括一個指向 GNU C 庫實作vprintf(或它的入口點)的鏈接,它是printf.
uj5u.com熱心網友回復:
printf在現實世界的 C 庫中總是使用 CDECL,因為 STDCALL 對于可變引數函式非常不方便,并且在某些情況下 ISO C 要求它無法正常作業。
ISO C 表示傳遞額外引數是定義明確的行為,例如printf("%d\n", 1, 2, 3);
Printf 必須安全地忽略它們,并且表現得像printf("%d\n", 1);. 這排除了像 STDCALL 這樣的被呼叫者彈出約定。(無論如何這對于任何可變引數函式都是不方便的,因為ret imm16在彈出 [er]IP 后增加 [er]SP 只能用于立即運算元,而不是注冊。所以你必須彈出回傳地址,將其復制到最高 4 或 8 個位元組的 args,然后ret從那里開始,即使您可以準確地計算出位置。)
主流呼叫約定不會單獨將 args 的數量(或它們在堆疊上的大小以位元組為單位)傳遞給可變引數函式,或使用任何型別的哨兵值,因此printf無法找出實際傳遞了多少 args . args 必須與格式字串匹配的 args 與格式字串參考的數量一樣多,但不要求不傳遞 args。
這就是為什么Windows C ABI / 實作總是對可變引數函式使用 CDECL 之類的約定,即使對于具有固定數量引數的函式,它們默認為 STDCALL。(32 位 FASTCALL 也是 callee-pops;Windows x64 不是,當前的 MS 檔案有時將其稱為 x64 fastcall 或“fastcall 約定”。)
此外,在 x86 32 位匯編中定義函式會很棒。
我這樣做是出于教育目的。
由于您這樣做是為了了解 asm,而不一定要創建 C 實作,printf這是一個非常復雜的 API 實作,對于純匯編專案可能不是一個好的選擇。(或者可以說,對于實際上不必是 ISO C 的任何現代設計;決議文本格式字串并通過可變長度的引數串列為簡單起見具有主要缺點。C 人認為每個物件都有單獨的函式呼叫你想要輸出的型別安全性和東西要好得多)
處理單個type -> string函式通常更容易,例如print_int(十進制)與print_int_hex(print_double實際上非??常復雜)與print_c_string(0 終止)與print_buffer(指標,長度)。
作為一個玩具專案,不要對 I/O 格式化功能目標太高。
首先提供一些簡單可用的,易于從 asm 呼叫的。Irvine32及其WriteDec(unsigned) vs. WriteInt(signed) vs.WriteString是玩具程式的一組輸出函式的一個很好的例子。Irvine32 尤其使用自定義呼叫約定,其中所有暫存器都保留呼叫(訓練輪模式),并且 arg 在 EAX 或 EDX 中(這非常好;堆疊 args 是愚蠢的,特別是對于只需要一個的函式)
另一個非常相似的例子是 MIPS 模擬器的MARS 系統呼叫。其中一些設計不佳(或故意給學生帶來不便?),例如它的讀取字串不回傳回傳值暫存器中的長度,只是將字符留在指向緩沖區中的終止 0 位元組(作為 C細繩)。所以如果你想知道你讀了多少,你必須遍歷它們尋找第一個0,即strlen.
這些玩具 API 沒有游標移動、沒有回聲的輸入或任何使真實終端和鍵盤處理方式更加復雜的東西。或任何指定格式的方法,如 printf0d用前導零填充到 20 位長。
如果你想撰寫自己的輸入/輸出函式,你可以考慮是否希望它們能夠與直接使用低級函式的代碼輕松混合,或者它們是否像 C stdio 那樣做自己的緩沖,并且應該是被視為不透明的 I/O 層,因此程式不應同時使用它們和較低級別的 OS 系統呼叫。
根據您想要的復雜程度,可能使用 args 來指定寬度限制,或者根據具體情況為專案定制。(畢竟,如果你想要可維護性和易于代碼重用,你不會一開始就選擇 asm。所以只需在執行它的地方實作 I/O 細節,而不是為呼叫者構建一個靈活的機制來請求任何格式)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/474098.html
