visual MSVC cl.exe尝试使用C++标头范例(定义与声明)



我正在尝试实现一个使用相当流行的C++标头范式的库(在.h/.hpp和.cpp文件中分隔定义和声明(。 我正在使用 MSVC cl.exe 编译器来定位 Windows 操作系统。

我显式使用 #include 指令,该指令针对表示定义的头文件abc.hpp。我希望编译器自动查找相应的abc.cpp文件,并将对abc.hpp::abc()的调用重定向到abc.cpp::abc()

(我知道我可以直接在示例中#include "abc.cpp",但请记住,我正在尝试解决一个更大的问题,即从它的头文件中包含整个库。

(我也尝试使用 /I 编译器选项,但在测试和阅读文档后,它没有多大用处。 https://learn.microsoft.com/en-us/cpp/build/reference/i-additional-include-directories

我在下面描述了我运行的测试和我得到的编译器错误:

######## main.cpp ######## 
#include <stdio.h> // printf_s
#include "abc.hpp"
int main(int argc, char *argv[]) {
    abc();
    return(0);
}
######## abc.hpp ######## 
void abc();
######## abc.cpp ######## 
#include "abc.hpp"
void abc() {
    printf("ABC!n");
}
######## command line ########
> cl main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.X for x64
Copyright (C) Microsoft Corporation.  All rights reserved.
main.cpp
Microsoft (R) Incremental Linker Version 14.X
Copyright (C) Microsoft Corporation.  All rights reserved.
/out:main.exe
main.obj
main.obj : error LNK2019: unresolved external symbol "void __cdecl abc(void)" (?abc@@YAXXZ) referenced in function main
main.exe : fatal error LNK1120: 1 unresolved externals

我对编译器自动重新映射的期望是否abc.hpp::abc() abc.cpp::abc()偏离了目标? 我是否被迫将.cpp代码作为单独的.lib编译器,以便能够将其/link /LIBPATH:给编译器?

我很高兴听到你对这件事的见解。

你有...让我们对C++编译和链接应该如何工作提出更合理的意见。但事实是,包含编译和链接模型C++是古老的,似乎与现代语言中的任何现代模块化构建都格格不入。然而,它被如此刻在C++上,这就是它与C++相伴的方式,从一开始就是这样。随着模块在 C++20 年被引入标准,情况即将(缓慢地(发生变化(考虑到 C++ 的遗产和向后兼容性,这确实是一项伟大而艰巨的成就(。


所以经典的方法是这样的:你包括包含你需要的声明的标头。 #include是一个预处理器指令,它或多或少只是标头内容的愚蠢复制粘贴。这使您可以单独编译每个"编译单元"。然后,将所有编译单元链接到最终的二进制文件中(将其作为库或可执行文件(。

假设您有一个关于函数foo定义foo.cpp

foo.cpp
int foo(int a)
{
     return a * a;
}

如果需要从另一个编译单元调用foo,则首先需要使foo声明在该编译单元中可见。您只需编写声明即可轻松完成此操作:

bar.cpp
int foo(int);
void bar()
{
    int r = foo(24);
}

在使用foo之前,编译器需要知道foo是什么。又名它需要知道它是一个采用 int 参数并返回 int 的函数。这就是我在上面的例子中写int foo(int)时所做的。豪尔,你不应该写每个声明。您可以在foo.hpp中编写一次foo声明,然后包含该标头:

foo.hpp
#pragma once // or standard-compliant guard
int foo(int);
bar.cpp
#include "foo.hpp"
void bar()
{
    int r = foo(24);
}

预处理器将在将文件内容馈送到编译器之前以文本方式将#include "foo.hpp"替换为文件的内容。

现在,您可以将bar.cpp编译为目标文件。编译器只是编写对一个符号的调用,foo已知该符号命名一个采用 int 参数并返回 int 的函数。但是,foo的定义是不可见的。即。编译器没有foo的"源代码"。

因此,下一步是将所有编译单元链接在一起。

您可以在两个单独的阶段进行编译和链接(我在示例中使用 g++,因为我更熟悉参数,但 cl.exe 的原理是相同的(:

# compile each compilation unit
g++ bar.cpp -o bar.o
g++ foo.cpp -o foo.o
# link all into the final executable
g++ bar.o foo.o -o my_executable

或者您可以一次性完成所有操作:

g++ foo.cpp bar.cpp -o my_executable

因为cl.exe(以及clanggcc(既不是编译器也不是链接器。它是一个前端,代表你调用预处理器、编译器和/或链接器。


您需要了解这些详细信息以了解幕后发生的事情,以便在出现问题时解决问题,但不应手动调用编译器。使用将为您处理所有这些问题的构建系统。让VS为您执行此操作或使用make/cmake。

最新更新