【问题标题】:Importing a C function导入 C 函数
【发布时间】:2020-10-15 20:27:42
【问题描述】:

以下导入语句有什么问题?我觉得我在这里搞砸了一些非常基本的东西:

// main.c
#include <stdio.h>
#include "1_square.h"

int main(int argc, char *argv[]) {
    int a=4;
    int a_squared=square(a);
    printf("Square of %d is %d\n", a, a_squared);
    return 0;
}
// 1_square.h
int square(int num);
// 1_square.c
int square(int num) {
    return num*num;
}
$ gcc 0_main.c -o 0_main && ./0_main

/usr/bin/ld: /tmp/cc6qVzAM.o: 在函数main': 0_main.c:(.text+0x20): undefined reference to square' collect2: error: ld 返回 1 个退出状态

或者gcc 是否需要引用1_square 才能构建它?

【问题讨论】:

  • 请注意,1_square.c 还应包括标题 1_square.h — 这样您就可以交叉检查函数定义是否与其他文件所告知的 square() 函数的类型相匹配。
  • @JonathanLeffler 好的——对于这样基本的东西,我应该摆脱1_square.c 文件并使用1_square.h 处理所有内容吗?
  • 我认为这是一个学习练习。您的大多数程序可能需要多个源文件。因此,您需要了解如何使用标头在源文件之间提供交叉检查,以及如何在单独的文件中编写代码以使其协同工作。是的,对于这个简单的示例,您可以不使用 1_square.h 标头和 1_square.c 源文件,只需将函数包含在与 main() 函数相同的源文件中即可。但这不太现实,练习的目的是让您了解如何处理多个源文件。

标签: c


【解决方案1】:

您需要将1_square.c 文件添加到您的构建命令中:

gcc -o 0_main 0_main.c 1_square.c && ./0_main

您需要函数的定义及其声明。

来自cmets:

为什么它需要在命令行中使用1_square.c,然后在0_main.c 标头中也需要1_square.h

就像 John Bollinger 在 cmets 中指出的那样,1_square.h1_square.c 的唯一共同点是它们的名称,这对编译器没有意义。就 gcc 而言,它们之间没有内在的关系。

让我们首先将所有内容放在一个源文件中:

/** main.c */
#include <stdio.h>

int square( int num );

int main( int argc, char **argv )
{
  int a = 4;
  int a_squared = square( a );
  printf( "Square of %d is %d\n", a, a_squared );
  return 0;
}

int square( int num )
{
  return num * num;
}

在 C 中,函数必须在源代码中调用之前声明;声明介绍了函数名称、它的返回类型以及它的参数的数量和类型。这将让编译器在翻译过程中验证函数调用是否正确编写,如果不是,则发出诊断,而不是等到运行时抛出错误。在这种特殊情况下,我们指定 square 函数采用单个 int 参数并返回 int 值。

这并没有说明 square 函数本身或其运作方式 - 这是由 square 函数的定义稍后提供的。

函数定义也用作声明;如果调用者和被调用函数都在同一个翻译单元(源文件)中,那么您可以将定义放在调用之前,而根本不必弄乱单独的声明:

/** main.c */
#include <stdio.h>

int square( int num )
{
  return num * num;
}

int main( int argc, char **argv )
{
  int a = 4;
  int a_squared = square( a );
  printf( "Square of %d is %d\n", a, a_squared );
  return 0;
}

这实际上更可取,因为您不必在两个不同的地方更新函数签名。这意味着您的代码“向后”读取,但老实说,这是一种更好的方法。

但是,如果您将函数分离到一个单独的源文件中,那么您需要有一个单独的声明:

/** main.c */
#include <stdio.h>

int square( int num );

int main( int argc, char **argv )
{
  int a = 4;
  int a_squared = square( a );
  printf( "Square of %d is %d\n", a, a_squared );
  return 0;
}

/** square.c */
int square( int num )
{
  return num * num;
}

在处理main.c 时,编译器不知道square.csquare 函数的定义 - 它甚至不知道文件存在 main.c 是否在 square.c 之前编译无关紧要,反之亦然;编译器一次对一个文件进行操作。关于square 函数,编译器唯一知道的是main.c 中的声明。

必须为单独的 .c 文件中定义的每个函数手动添加声明是一件很痛苦的事情 - 您不想为 printfscanffopen 等编写单独的声明. 所以按惯例我们创建一个单独的.h 文件,与.c 文件同名来存储声明:

/** main.c */
#include <stdio.h>
#include "square.h"

int main( int argc, char **argv )
{
  int a = 4;
  int a_squared = square( a );
  printf( "Square of %d is %d\n", a, a_squared );
  return 0;
}

/** square.h */
int square( int num );

/** square.c */
int square( int num )
{
  return num * num;
}

按照惯例,我们还在 .h 文件中添加 include 保护 - 这样可以防止每个翻译单元多次处理文件的内容,如果您 #include "square.h" 可能会发生这种情况并包含另一个标题,包含square.h

/** square.h */
#ifndef SQUARE_H       // the contents of this file will only be
#define SQUARE_H       // processed if this symbol hasn't been defined

int square( int num );

#endif

同样按照惯例,我们将.h 文件包含在.c 文件中,以确保我们的声明与我们的定义一致——如果不符合,编译器会报错:

/** square.c */
#include "square.h"

int square( int num )
{
  return num*num;
}

main.csquare.c 都被编译后,它们的目标代码将被链接到一个可执行文件中:

main.c ------+-----> compiler ---> main.o ----+--> linker ---> main
             |                                |
square.h ----+                                |
             |                                |
square.c ----+-----> compiler ---> square.o --+

我们必须编译两个 C 文件并将它们的目标代码链接在一起,以获得一个工作程序。毫无疑问,您的 IDE 让这一切变得简单——您只需将源文件添加到项目中,它就会正确构建它们。 gcc 可让您在一个命令中完成所有操作,但您必须列出项目中的所有 .c 文件。

如果您从命令行运行,您可以使用make 实用程序来简化操作。您需要创建一个如下所示的 Makefile:

CC=gcc
CFLAGS=-std=c11 -pedantic -Wall -Werror
main: main.o square.o
all: main
clean:
        rm -rf main *.o

您需要做的就是在命令行输入make,它会使用内置规则编译main.csquare.c 来构建您的项目。

【讨论】:

  • 我明白了,我认为当我使用 IDE 时,它在幕后为我做了很多事情。那么为什么它需要在命令行中使用1_square.c,然后在0_main.c 标头中也需要1_square.h
  • 因为@samuelbrody1249,只有约定将1_square.h的内容与1_square.c的内容连接起来。不需要任何对应关系,因此编译器不能假设它应该编译后者,因为另一个源文件包含前者。
  • @JohnBode 哇,真是个好答案。非常感谢您花时间。
猜你喜欢
  • 1970-01-01
  • 2016-05-23
  • 2013-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多