C 头文件 鸡还是蛋的问题

C header file The chicken or The egg problem

我正在开发一个 c 程序(一个合成器),我突然 运行 遇到了问题。

问题是:

我有两个头文件 server.h 和 seat.h,每个头文件由一个结构组成。

server.h

typedef struct
{
    const char *socket;
    struct wl_display *wl_display;
    struct wl_event_loop *wl_event_loop;
    struct wlr_backend *backend;
    struct wlr_renderer *renderer;
    struct wlr_compositor *compositor;
    struct wlr_output_layout *output_layout;

    Seat *seat; // the seat struct from seat.h

    struct wl_list outputs;

    struct wl_listener output_listener;
} Server;

bool init_server(Server *server);
void run_server(Server *server);
void destroy_server(Server *server);

seat.h

typedef struct
{
    Server *server;  // the server struct from server.h
    struct wlr_seat *wlr_seat;

    struct wl_listener input_listener;
    struct wl_listener destroy_seat;
} Seat;

Seat *create_seat(Server *server);
void handle_new_input(struct wl_listener *listener, void *data);
void destroy_seat(struct wl_listener *listener, void *data);

主要问题是它创建了一个头文件循环,所以当我编译它时会导致错误。

我已经阅读了这里的问题 C header file loops。我试过它在 struct 的情况下有效但是当我调用 create_seat() 函数时它告诉我类型不匹配。在我的例子中,我也使用 typedef,所以有点混乱。

由于实际代码在任何机器上都不好运行(因为它需要依赖等)请使用此代码作为参考,这解释了我的实际问题。

我使用介子构建系统。如果我使用 ninja 编译程序,它会以无限循环结束。

代码如下:

main.c

#include <stdio.h>
#include "server.h"
#include "seat.h"

int main()
{
    Server server;
    server.id=10;

    Seat seat;
    seat.id=20;

    server.seat=seat;
    seat.server=server;

    printSeatId(server);
    printServerId(seat);
    return 0;
}

server.h

#include "seat.h"
typedef struct
{
    Seat seat;
    int id;
} Server;

void printSeatId(Server s);

seat.h

#include "server.h"
typedef struct
{
    Server server;
    int id;
} Seat;

void printServerId(Seat s);

server.c

#include <stdio.h>
#include "server.h"
void printSeatId(Server s)
{
    printf("%d",s.seat.id);
}

seat.c

#include <stdio.h>
#include "seat.h"
void printServerId(Seat s)
{
    printf("%d",s.server.id);
}

meson.build - 在 src 文件夹中

sources = files(
  'main.c',
  'server.c',
  'seat.c'
)

executable(
  'sample',
  sources,
  include_directories: [inc],
  install: false,
)

meson.build 在项目文件夹中

project(
  'sample',
  'c',
  version: '1.0.0',
  meson_version: '>=0.56.0',
  default_options: ['c_std=c11','warning_level=2'],
)

add_project_arguments(
  [
    '-DWLR_USE_UNSTABLE',
    '-Wno-unused',
    '-Wno-unused-parameter',
    '-Wno-missing-braces',
    '-Wundef',
    '-Wvla',
    '-Werror',
    '-DPACKAGE_VERSION="' + meson.project_version() + '"',
  ],
  language: 'c',
)

cc = meson.get_compiler('c')
inc = include_directories('include')
subdir('src')

目录结构如下:

<project_folder>
     |--->src
     |     |--->server.c
     |     |--->seat.c
     |     |--->meson.build
     |
     |--->include
     |     |--->server.h
     |     |--->seat.h
     |
     |--->meson.build

我已经给出了与原项目相同的目录结构

如果您只需要一个指向另一个 struct 的指针,您可以像这样转发声明那个:

typedef struct Server Server;

typedef struct
{
    Server *server;  // the server struct from server.h
    struct wlr_seat *wlr_seat;

    struct wl_listener input_listener;
    struct wl_listener destroy_seat;
} Seat;

解决冲突的方法是添加一个不完整结构类型的前向声明。前向声明中使用的 struct 类型需要一个标签,同一标签用于同一类型的完整声明。为了对称,添加 SeatServer 结构类型的前向声明是有意义的。 typedef 类型定义可以移动到 seat.hserver.h[ 包含的一个或多个新头文件中=40=]头文件。头文件需要定义guard macros以避免多重定义冲突。

例如:

seat_server_t.h

#ifndef SEAT_SERVER_T_H__INCLUDED
#define SEAT_SERVER_T_H__INCLUDED

typedef struct Seat_s Seat;
typedef struct Server_s Server;

#endif

seat.h

#ifndef SEAT_H__INCLUDED
#define SEAT_H__INCLUDED

#include "seat_server_t.h"

/* Other includes for struct wl_listener, etc. here... */

struct Seat_s
{
    Server *server;
    struct wlr_seat *wlr_seat;

    struct wl_listener input_listener;
    struct wl_listener destroy_seat;
};

Seat *create_seat(Server *server);
void handle_new_input(struct wl_listener *listener, void *data);
void destroy_seat(struct wl_listener *listener, void *data);
#endif

server.h

#ifndef SERVER_H__INCLUDED
#define SERVER_H__INCLUDED

#include "seat_server_t.h"

/* Other #include's for struct wl_display, etc. here... */

struct Server_s
{
    const char *socket;
    struct wl_display *wl_display;
    struct wl_event_loop *wl_event_loop;
    struct wlr_backend *backend;
    struct wlr_renderer *renderer;
    struct wlr_compositor *compositor;
    struct wlr_output_layout *output_layout;

    Seat *seat; // the seat struct from seat.h

    struct wl_list outputs;

    struct wl_listener output_listener;
};

bool init_server(Server *server);
void run_server(Server *server);
void destroy_server(Server *server);

#endif

新的头文件seat_server_t.h不需要main.c[=等.c文件直接包含40=]。它只能被其他头文件 seat.hserver.h 视为内部文件。如果需要,它也可以分成两个单独的头文件(每个头文件一个 typedef)。