通过gcc命令行进行C编译时出现问题



我目前正在尝试在不使用IDE的情况下了解C编译的基础知识。由于我只是用IDE学习C和嵌入式编程,所以我认为这是一个好主意,可以让我更好地了解整个构建过程是如何在幕后工作的。我主要想学习如何为STM32控制器实现一个完整的无IDE工具链。

因此,我的想法是从简单开始,尝试理解纯C构建工具链及其可能的配置。为此,我搜索了教程,找到了这个和这个。

我试着遵循我的windows系统的第一个教程,但很早就遇到了一些问题,我很难理解。

我创建了以下hello.c测试文件:

#include <stdio.h>
#include <stdint.h>
int main ( void )
{
printf("Hello World!n");
return 0;
}

首先,我尝试了使用gcc -o hello.exe hello.c(教程中的1.6)进行简单的完整编译一切都很好,所以我决定一个接一个地测试编译步骤(教程中的1.7)

我按以下顺序调用了所有命令:cpp hello.c > hello.i (preprocessing)->gcc -S hello.i (Compilation)->as -o hello.o hello.s (Assembly)->ld -o hello.exe hello.o (Linking)

链接之前的每一步似乎都有效,但链接器会给我以下错误:

ld:hello.o:hello.c:(.text+0xa):对"__main"ld:的未定义引用

hello.o:hello.c:(.text+0x47):未定义对"放置"ld:的引用

hello.o:hello.c:(.text+0x5c):对`printf'的未定义引用

我这里做错了什么吗?">quot;运算符用于预处理和组装,但如果我只是使用gcc -o hello.exe hello.c进行编译,则不使用

甚至有人经常单独使用这些步骤吗?我读到我也可以使用gcc -E main.c > main.i而不是cpp hello.c > hello.i,那么为什么要使用cpp命令,有什么好处吗?

接下来,我把这个问题放在一边,并尝试添加includes。为此,我创建了以下2个文件:myFunc.c:

uint8_t myFunc( uint8_t param )
{
uint8_t retVal = 0;
retVal = param + 1;
return retVal;
}

myFunc.h

#include <stdint.h>
uint8_t myFunc( uint8_t param );

并将hello.c改为:

#include <stdio.h>
#include <stdint.h>
#include "myFunc.h"
int main ( void )
{
uint8_t testVal = 0;
testVal = myFunc(testVal);
printf("Hello World!n");
printf("Test Value is %d n", testVal);
return 0;
}

我第一次尝试gcc -o hello.exe hello.c,但得到错误:

未定义对"myFunc"collect2.exe的引用:错误:ld返回1退出状态

所以我想我应该添加include路径(即使它是同一个目录)。经过短暂的搜索和第二个网站的帮助,我尝试了gcc -Wall -v -IC:UsersUserDesktopC-Only_Toolchain hello.c -o hello.exe但是得到同样的错误。。。

添加包含路径的方式有问题吗?(显然是的)

最后,我尝试测试教程中的GNUmake命令。我打开编辑器并插入教程中显示的所有内容。当编辑器将文件保存为.txt编辑器时,我试图删除文件扩展名。

生成文件如下所示:

all: hello.exe
hello.exe: hello.o
gcc -o hello.exe hello.o
hello.o: hello.c
gcc -c hello.c
clean:
rm hello.o hello.exe

但是如果我在控制台中输入CCD_ 11;制造";写得不正确或找不到。正如教程所建议的那样,我使用制表符进行缩进,但它甚至不会识别出有makefile。这是因为在我删除扩展名之前,它最初是一个.txt文件吗?

如果有人能帮我解决这个相当简单的问题,我会很高兴。。。此外,如果你能就如何更有效地进入这个话题提出一些好的建议,或者有一些好的来源可以分享,我将不胜感激。

提前感谢并保持健康:)

致以最诚挚的问候Evox402

所以,这些问题很多。(在下面的文章中,我使用linux,所以有些输出只是相似的,而不是完全相同的,比如路径和汇编输出,但由于您使用了gcc,它可以很好地转移到windows)。

我按以下顺序调用了所有命令:cpp-hello.c>hello.i(预处理)->gcc-S hello.i(编译)->as-o hello.o hello.s(程序集)->ld-o hello.exe hello.o(链接)

作为重复:你在这里做什么?

cpp hello.c > hello.i

您可以在C文件上运行预处理器。它只是对宏/#define进行文本替换,并包含文件。

这个看起来像这样。(缩短了一点,因为它有大约800行)

...Snip....
struct _IO_FILE;
typedef struct _IO_FILE FILE;
struct _IO_FILE
{
int _flags;

char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base;
char *_IO_write_ptr;
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;

char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset;

unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;



__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
...Snip...
extern int printf (const char *__restrict __format, ...);
...Snip...
int main ( void )
{
printf("Hello World!n");
return 0;
}

现在,所有重要的定义都包含在内,因此C编译器可以运行。gcc -S hello.i。它只是将C代码转换为汇编。(在windows上看起来会有点不同)

.file   "hello.c"
.text
.section    .rodata
.LC0:
.string "Hello World!"
.text
.globl  main
.type   main, @function
main:
.LFB0:
.cfi_startproc
pushq   %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
leaq    .LC0(%rip), %rdi
call    puts@PLT
movl    $0, %eax
popq    %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size   main, .-main
.ident  "GCC: (Debian 10.2.0-17) 10.2.0"
.section    .note.GNU-stack,"",@progbits

现在您必须将汇编代码转换为机器代码:

as -o hello.o hello.s

这个命令只生成一个所谓的对象文件,其中包含链接器所需的代码和重要元数据。

ld -o hello.exe hello.o

现在调用链接器,对象文件作为参数,hello.exe作为输出文件。它将查找入口点(类似linux的_start,例如windows上的WinMain,或者有时是_main)。但是C标准库中的函数也缺失了。但为什么呢?你没有说链接器,你想包括它。如果你像调用链接器ld一样显式,你必须传递所有你想包括的库。例如,您必须添加-lc以包括stdlib,依此类推

我在这里做错了什么吗?

您只是忘记将C库添加到链接器应该与对象文件链接的库中。

>quot;操作员用于预处理

>不是来自cpp。它来自外壳。尝试在没有> hello.i的情况下运行。预处理器只会在控制台上输出它。>重定向到指定的文件(此处为hello.i)。

我也可以使用gcc -E main.c > main.i,那么为什么要使用cpp命令,有什么好处吗?

没有区别。gcc在内部调用预处理器。

是否经常单独使用这些步骤?

这些步骤有时在makefile中使用,但不像您那样分开,但通常只在编译+链接时作为两个单独的步骤使用,以减少编译时间。

首先尝试了gcc-o hello.exe hello.c,但得到了错误:

C编译器知道,它进行编译,至少有一个myFunc的定义,因此,它会发出有效的汇编代码。但是链接器一旦解析了对函数的引用,就找不到它并发出错误。您必须将myFunc.c添加到命令行:

gcc -o hello.exe hello.c myFunc.c

但是如果我在控制台中输入make;制造";写得不正确或找不到。正如教程所建议的那样,我使用制表符进行缩进,但它甚至不会识别出有makefile。这是因为在我删除扩展名之前,它最初是一个.txt文件吗?

您必须将make.exe的目录添加到路径中。假设它有路径:

C:Foobarbazmake.exe

然后将其添加到路径中(在命令行中执行):

set PATH=%PATH%;C:Foobarbaz

这只会在关闭命令行之前起作用,或者你可以按照这里的概述永久设置它。

最新更新