通过gcc命令行进行C编译的问题

Trouble with C compilation via gcc command line

我目前正在尝试在不使用 IDE 的情况下了解有关 C-compilation 的基础知识。 因为我只学习了 C- 和 embedded-programming 和 IDE 我认为学习它是个好主意,让我更好地理解整个构建过程在幕后是如何工作的。 我主要想学习如何为 STM32 控制器实现完整的 IDEless 工具链。

所以我的想法是从简单开始,尝试理解 C-only 构建工具链及其可能的配置。为此,我搜索了教程并找到了 this and this 个。

我试图在我的 windows 系统上按照 first tutorial 进行操作,但很早就遇到了一些我无法理解的问题。

我创建了以下 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): undefined reference to `__main' ld:

hello.o:hello.c:(.text+0x47): undefined reference to `puts' ld:

hello.o:hello.c:(.text+0x5c): undefined reference to `printf'

我是不是做错了什么?并且“>”运算符用于预处理和汇编是有原因的,但如果我只是使用 gcc -o hello.exe hello.c

进行编译则不是

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

接下来我把这个问题放在一边并尝试添加包含。 为此,我创建了以下 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 但得到错误:

undefined reference to `myFunc' collect2.exe: error: ld returned 1 exit status

所以我想我应该添加包含路径(即使它是同一个目录)。 经过短暂的搜索和 second site 的帮助,我尝试了 gcc -Wall -v -IC:\Users\User\Desktop\C-Only_Toolchain hello.c -o hello.exe 但是得到同样的错误...

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

最后我尝试从 tutorial 测试 GNU make 命令。 我打开编辑器并插入教程中显示的所有内容。 当编辑器将文件保存为 .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

但是如果我在我的控制台中输入 make,我会收到命令“make”写错或找不到的错误。 正如教程所建议的那样,我使用制表符进行缩进,但它甚至不会识别出有一个 makefile。 是因为我删除扩展名之前它本来是一个.txt文件吗?

如果有人能帮助我解决我对这个相当简单的问题的困惑,我会很高兴...... 此外,如果您对如何更有效地进入这个主题有一些好的建议或者有一些好的资源可以分享,我将非常感激。

在此先感谢您,祝您身体健康:)

最好的问候 Evox402

所以,这些问题很多。 (在下面我使用 linux,所以一些输出只是相似的,而不是相同的,比如路径和汇编输出,但是因为你使用 gcc,它完全可以转移到 windows ).

I called all commands in the following order: 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)

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

cpp hello.c > hello.i

您 运行 C 文件的预处理器。它只是对宏/#defines 进行文本替换并包含文件。

看起来像这样。 (因为它有大约 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    [=12=], %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

此命令仅生成一个包含您的代码和重要元数据的所谓目标文件,linker 将需要。

ld -o hello.exe hello.o

现在您调用 linker,将目标文件作为参数,将 hello.exe 作为输出文件。它将寻找入口点(_start 在 linux 上,例如 WinMain 在 windows 上,有时 _main)。 但也缺少 C 标准库中的函数。 但为什么?您不会说 linker,您想要包含它。如果像以前那样显式地调用 linker ld,则必须传递所有要包含的库。 您必须添加例如 -lc 以包含 stdlib,等等。

Did I do something wrong here?

您只是忘记将 C 库添加到 link 用户应该 link 使用您的对象文件的库中。

And is there a reason the ">" operator is used for preprocessing

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

I could also use gcc -E main.c > main.i so why use the cpp command, are there any advantages?

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

Do one even use these steps seperately that often?

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

first tried the gcc -o hello.exe hello.c but get the error:

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

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

But if I enter make in my console I get the error that the command "make" is written incorrectly or could not be found. I used tab for the indentation just as the tutorial suggests but it will not even recognize that there is a makefile. Is this because it was originally a .txt file before I deleted the extension?

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

C:\Foo\bar\baz\make.exe

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

set PATH=%PATH%;C:\Foo\bar\baz

这只会在您关闭命令行之前有效,或者您可以将其永久设置为 outlined here 例如。