【问题标题】:Why the executable is not executable?为什么可执行文件不可执行?
【发布时间】:2013-03-18 14:46:09
【问题描述】:

我有一个 hello world cpp 文件。如果我通过c++ test.cpp -o test 编译它,我会得到可执行的“测试”文件(-rwxr-xr-x),如果我执行它,它会被执行并生成预期的结果。

但是,如果我使用${CXX} -std=c++0x -I${INCLUDE_DIR1} -c test.cpp -o test -L{LIB_DIR1} -llib_name,我也会得到“测试”文件,但在这种情况下它是不可执行的。所以,我无法执行它。我尝试chmod +x,它获得了执行权限,但如果我尝试执行它会收到错误消息(无法执行)。

我做错了什么以及如何纠正?

【问题讨论】:

  • file test 看看它是什么类型 - 可能只是一个错误命名的 .o 文件。
  • 如果我输入file test,我会得到test: ELF 64-bit LSB relocatable, x86-64, version 1 (GNU/Linux), not stripped,但我不知道它是什么意思。此输出是否意味着我有一个“命名错误的 .o 文件”?

标签: c++ compilation executable


【解决方案1】:

-c 告诉编译器生成可执行文件(这意味着“仅编译”)。它只创建一个适合链接到可执行文件的目标文件(可能与其他目标文件和库)。

如果您想要一个可执行文件,请移除 -c 开关。

完整编译过程的详细信息,请参见:How does the compilation/linking process work?

【讨论】:

  • 如果我删除 -c 我得到:/usr/bin/ld: cannot find -llib_name collect2: error: ld returned 1 exit status.
  • 这只是意味着编译器找不到那个库。如果它不在默认搜索路径中,则需要使用-L/path/to/lib 指定它所在的目录。
【解决方案2】:

您似乎正处于整个编程疯狂的开始阶段。所以,如果你很难理解什么是错误命名的对象(在这种情况下称为 .o)文件、编译器参数以及它们的实际作用,甚至是库名称,我确实理解。

我的假设是,从外观上看,您正在复制您正在执行的 shell 脚本行,以便从未知示例生成可执行文件(至少-llib_name 让人感觉如此)。所以,我会尽量简单、清楚、粗略地解释这里发生的一切。

首先,C++ 编译器将给定的源文件编译成可重定位的机器代码。如果您将编译器(和单独的编译器)的输出存储在一个文件中,该文件将成为上述目标文件(请记住 .o 文件)。如果你注意了,这意味着一个目标文件包含可重定位的机器代码

现在你已经编译了你的源代码,并且你已经将它的机器代码等效地存储在一个目标文件中。然而,这个目标文件不是直接可执行的;即使它确实包含 CPU 执行时应该没有问题的机器代码。问题是,这个机器代码没有链接。所以,第二个要点是:目标文件中的可重定位机器代码没有链接,因此不能(好吧,更像不应该)被执行。除此之外,目标文件还可能包含其他元数据,以帮助链接器进行链接过程,从而生成实际的可执行文件。我们将把这个中间表示的整体称为目标代码

所以,既然我们编译了源代码并发出了目标文件,下一个明显且合乎逻辑的步骤是将目标文件与您想要的任何库和/或其他目标文件链接,以生成我们想要的可执行文件可以像您的第一个 ./test 二进制文件一样执行。这就是链接器发挥作用的地方,链接器接受目标文件和库(旁注:大致来说,库是目标文件的集合)并执行一些链接魔术,例如跨模块解析目标文件中的未定义引用并安排地址空间可执行文件等。然后链接器发出一个最终的可执行文件,该文件符合调用链接器的目标平台的可执行文件格式。发出的正是这个文件,您可以像 ./test 一样执行。

所以,既然您已经了解了基础知识,那么让我们看看您的 shell 调用有什么问题。首先,您应该知道通过调用c++ 可以同时触发C++ 编译器(我认为在这种情况下是g++clang++链接器。其次,您应该知道-c 标志告诉编译器编译给定的源文件并单独发出其等效的目标代码和机器代码。因此,调用 C++ 编译器没有 -c 标志不仅会导致它编译给定的源代码,还会链接生成的目标代码并生成最终的可执行文件。这就是您的第一个命令行中发生的情况。发出的文件名为test 是链接的可执行文件。但是,在您的第二个命令行中存在 -c 标志。在这种情况下,编译器只会发出通过编译test.cpp 生成的目标代码。您需要链接此生成的目标文件以生成可执行文件。看起来你告诉编译器将它生成的目标代码存储在一个名为 test 的文件中,这是 Marc 在提到错误命名时所说的(目标文件通常具有 .o.obj 后缀)。

从现在开始,您有两个选择,要么删除-c 标志,要么链接目标文件。要编译和链接(单独)目标文件(包括 probably-it-shouldn't-be-there™ liblib_name):

${CXX} -std=c++0x -stdlib=libc++ -I${INCLUDE_DIR1} -c test.cpp -o test.o
${CXX} -std=c++0x -stdlib=libc++ -L${LIB_DIR1} -llib_name test.o -o test

我冒昧地在此介绍libc++。如果您愿意使用 C++11(您将其称为 C++0x),最好有它。然而,如果你想在一行中编译和链接,同样的事情可以用这个来实现:

${CXX} -std=c++0x -stdlib=libc++ -I${INCLUDE_DIR1} -L${LIB_DIR1} -llib_name test.cpp -o test

这两个做完全相同的事情。但请注意,单行版本缺少-c 标志。

现在我们几乎涵盖了所有内容,最后一个问题将是 -I-L-l 的作用。 -I 指定编译器将搜索源代码中包含的头文件的路径。此路径不会覆盖编译器的默认标头搜索路径,它只是作为它们的补充。 -L 是一回事,但对于图书馆来说。您的编译器将在此路径中查找您明确链接的库(例如 liblib_name 在这种情况下)。同样,这不会覆盖默认库搜索路径。最后,-l 标志用于在链接可执行文件时将库指定给链接器以链接。在您的情况下,您正在链接一个名为 liblib_name 的库(为 -l 标志指定的名称省略了库文件名中的第一个 lib),您说编译器抱怨它不存在。您确定要将liblib_name 链接到您的可执行文件中吗?这甚至是一个真正的图书馆吗?如果不是,则删除整个 -llib_name 参数,您的编译器应该停止为此大喊大叫。

抱歉,很长,很长,很长的解释,但我希望你在这里学会了一点点,在那里学会了一点点,所有这些都会对你有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多