如果您在 32 位模式 (-m32
) 下使用
TinyCC来编译使用 msvcrt.dll 中的任何 CRT 函数/符号的示例程序(例如本问题后面提供的代码片段),您'会遇到编译失败:
tcc.exe -std=c11 -Wall -Werror -Wl,-subsystem=console -m32 .\main.c
(这只发生在
-m32
下,而 -m64
工作得很好。)
_iob
也不是唯一“未解决”的符号; printf
、freopen
、freopen_s
,基本上来自 CRT 的所有内容都将无法链接。
无论您是否使用
-lmsvcrt
、#pragma comment(lib, "msvcrt")
、_declspec(dllimport)
、attribute ((dllimport))
、-static
或-shared
,甚至在C:\Windows\SysWow64\msvcrt.dll上使用
-impdef
(或其早期版本:msvcrt40.dll),TCC *仍然*抱怨。
我已经用
DUMPBIN.exe
验证了 32 位和 64 位 msvcrt.dlls do,实际上都定义了 _iob
和其他符号。
通过一些神秘的逻辑,以下内容完全可以正常工作:
tcc.exe -std=c11 -Wall -Werror -Wl,-subsystem=console -m64 .\main.c
main.c
//#pragma comment(lib, "msvcrt") //__attribute__((dllimport)) extern __declspec(dllimport) FILE _iob[]; #include <windows.h> // _MSVCRT_ being defined will cause MinGW's stdio.h to use _iob as // opposed to _imp___iob; only the former is defined in msvcrt.dll. // However, even though _iob is exported by both the 32- and 64-bit // versions of said dll, TinyCC still fails to find _iob in the former. #define _MSVCRT_ #include <stdio.h> void main() { // AllocConsole() and basically everything from kernel32.dll or // user32.dll work perfectly fine, both in -m32 and -m64; it's // only msvcrt.dll that causes issues with TinyCC. AllocConsole(); // Any CRT function (e.g., freopen, freopen_s, printf, etc.) // fail to get linked properly ONLY in -m32; -m64 is fine. // Even if I change the -I and -L paths to C:/Windows/SysWow64 // and/or use tcc.exe -impdef to create .def files from them, // TCC still fails in finding _iob and other symbols. // Also, using #pragma comment(lib, "msvcrt") or -lmsvcrt // doesn't help at all. Even if you do get TCC to somehow // stop complaining about missing symbols, it'd just include // a blank IAT.printf or IAT.freopen, causing segfaults. freopen("CONOUT$", "w", stdout); printf("This only compiles (and prints) under TCC in 64-bit mode."); }
如前所述,无论其他开关如
-m32
、-std
、-shared
、-static
、-lmsvcrt
等,-subsyetem
中的错误都会发生。所以,在这一点上,我开始认为这可能确实是 TinyCC 0.9.27 (Win32 & Win64 构建)本身。
经过近16个小时的调试,我找到了罪魁祸首:
_iob
和_imp___iob
应该用__attribute__((dllimport))
或__declspec(dllimport)
来声明。
具体来说,TCC的./include目录中的第
#ifndef _STDIO_DEFINED
#ifdef _WIN64
_CRTIMP FILE *__cdecl __iob_func(void);
#else
#ifdef _MSVCRT_
extern FILE _iob[]; /* A pointer to an array of FILE */
#define __iob_func() (_iob)
#else
extern FILE (*_imp___iob)[]; /* A pointer to an array of FILE */
#define __iob_func() (*_imp___iob)
#define _iob __iob_func()
#endif
#endif
#endif
修复它们,使其在
-m32
下编译得一样好
在-m64
下,您需要将它们更改为以下内容:
#ifndef _STDIO_DEFINED
#ifdef _WIN64
_CRTIMP FILE *__cdecl __iob_func(void);
#else
#ifdef _MSVCRT_
__attribute__((dllimport)) extern FILE _iob[]; /* A pointer to an
array of FILE */
#define __iob_func() (_iob)
#else
__attribute__((dllimport)) extern FILE (*_imp___iob)[]; /* A
pointer to an array of FILE */
#define __iob_func() (*_imp___iob)
#define _iob __iob_func()
#endif
#endif
#endif
(注意:正如我之前所说,
__attribute__((dllimport))
和
__declspec(dllimport)
在这里工作。 他们中的任何一个都可以解决这个问题
_MSVCRT_
和非 _MSVCRT_
标头。)
关于为什么此修复首先有用的一些其他详细信息:
freopen()
在 -subsystem=console
(即 CLI)中是可选的,但是
如果您在 -subsystem=windows
(即 GUI)中编译,则绝对需要
模式;如果后者没有调用freopen()
,则printf()
和其他流
输出不会显示在分配的控制台上;freopen()
取决于 stdout
的值,stdout
是
基于 ;中的
_iob
_imp___iob
_imp___iob
太旧,不再存在于较新的 msvcrt.dll 或 ucrtbase.dll 中,使得 _iob
(因此定义 _MSVCRT_
)成为首选;和#define
类似于根本不定义它;我认为 在_MSVCRT_
下工作得很好。 我还不确定这是否是
TCC特定的补丁,或者它是否实际上影响任何使用 MinGW 中的
-m64
从 2024 年 10 月 24 日起编译了最新的 mob 分支,但是这个
错误对它的影响与影响 0.9.27 的方式相同。)