C - 将代码正确划分为多个文件

C - properly partitioning the code into multiple files

我正在用 C 语言编写一个程序,我将各个部分分开放在不同的 .c 文件(模块)中。

一些模块(例如 B 和 C)包含相同的模块(例如 A),当这些模块(B 和 C)被另一个模块(例如 D)包含时,会形成菱形 "hierarchy"导致重新定义问题。

例如

vec3.c

#include <math.h>

typedef struct {
    float x, y, z;
} vec3;

float dot(vec3 v1, vec3 v2) {
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}

/* ... other stuff ... */

ray.c

#include "vec3.c"

typedef struct {
    vec3 A, B;
} ray;

vec3 origin(ray r) {
    return r.A;
}

/* ... other stuff ... */

sphere.c

#include "ray.c"

typedef struct {
    float t;
    vec3 p;
    vec3 n;
} record;

typedef struct {
    vec3 center;
    float radius;
} sphere;

typedef enum {false, true} bool;

bool hit_sphere(sphere s, ray r, float tmin, float tmax, record *rec) {
    /* ... do stuff ... */
}

/* ... other stuff ... */

camera.c

#include "ray.c"

typedef struct {
    vec3 origin;
    vec3 lower_left_corner;
    vec3 horizontal;
    vec3 vertical;
} camera;

/* ... other stuff ... */

vec3 origin = {0.0, 0.0, 0.0};
vec3 lower_left_corner = {-2.0, -1.0, -1.0};
vec3 horizontal = {4.0, 0.0, 0.0};
vec3 vertical = {0.0, 2.0, 0.0};

camera cam = {origin, lower_left_corner, horizontal, vertical};

main.c

#include <stdio.h>
#include <float.h>
#include "sphere.c"
#include "camera.c"

/* ... other stuff ... */

int main() {
    /* ... do stuff ... */
}

在这种情况下会出现问题,因为 camera.c 和 sphere.c 都包含 ray.c(因此也包含 vec3.c),因此存在结构的双重定义和这些模块中定义的函数。

如何重新排列代码才能避免这些问题?

谢谢!

您永远不应包含 .c 个文件。

为每个实现文件(.c)创建一个头文件(.h)并在其中声明函数。然后将头文件包含在相应的实现文件中。声明并包含后,您应该在实现文件中编写这些声明函数的实现。

然后使用包含头文件的函数。

头文件示例 (example.h):

#ifndef EXAMPLE_H
#define EXAMPLE_H

/* Function declarations */
void example();

#endif /* EXAMPLE_H */

实施文件示例 (example.c):

#include "example.h"

void example() {
    /* TODO: add logic */
}

关于文件组织没有 language-defined 规则。实际上,包含在源文件中的所有文件都是由预编译器创建的 so-called "compilation unit" 形式,并被编译器视为单个文件。预编译器的指令 #include 确实是这样做的……将其他文件包含到正在处理的当前文件中。因此,必须确保文件只包含一次。

一个经典的方法是使用预处理器的#ifndef 指令。大多数编译器支持指令 #pragma once 但它不是标准工具,在循环包含的情况下有时会出现不可预测的行为。

调用包含可重用声明和定义的文件 "headers" 并为它们提供 .h(有时为 .hpp)是公认的做法。 C++ 标准 headers 同意完全没有扩展以避免将它们与 C headers 混合。例如。 stdio.h 被 cstdio 取代,前者不应在 C++ 中使用。有时需要包含大量重复定义,可以得到不同的扩展名 .inc、.incl 等。

那么为什么人们会说 "never include .c files"。它与所使用的工具链有关,即控制应用程序构建过程的一组实用程序。您几乎永远不会 运行 手动编译。总是有一个构建工具通常决定如何处理 file ,该决定基于扩展名。 .c 文件通常被认为代表单独的编译模块,因此工具会 运行 编译它们中的每一个,然后再尝试 link 它们在一起。

Headers 不应该定义 objects 之类的函数或变量,他们可以声明它们用于外部 linking。为什么?如果将带有已定义变量的 header 文件包含到多个编译单元中,您会从 linker 那里得到一个错误,因为所有单元都将包含相同的符号。因此,定义应该是唯一的或程序被认为是 ill-formed。默认情况下,函数始终假定具有外部 linking

/* myheader.h */
#ifndef ___MYHEADER_H
#define ___MYHEADER_H

extern int globalVariable;

typedef struct MyType {

};

int foo(MyType);

#endif

/* myheader.cpp */
#include "myheader.h"

int globalVariable = 5;

int foo(MyType param)
{
/* body of foo */
}

项目中文件的组织方式由设计者决定。如果您是团队的一员,您应该遵循该团队的首席设计师或他们批准的文档的建议。