You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GCC单编译单元中检查头文件包含方式并限制直接引用?

基于CMake+GCC实现头文件层级依赖检查

示例代码结构

a.cc

// a.cc  
#include "a.h"

a.h

// a.h  
#include "b.h"  
#include "c.h"

b.h

// b.h  
#include "c.h"

c.h

// c.h
#include <string>

问题背景

通过gcc -H命令可观察到:"b.h"由当前编译单元直接处理,"c.h"为间接引入。

核心需求

禁止开发者在a.cc这类源文件中直接包含"c.h",需通过CMake+GCC工具链实现类似Bazel的layercheck层级检查功能(类比Clang的-fmodules-decluse特性)。

现有尝试瓶颈

GCC提供了PLUGIN_INCLUDE_FILE插件事件,但与Clang不同,当前获取到的事件信息不足以实现精准检查(可能未掌握正确用法)。

补充规则

若头文件与源文件基础名称相同(如xx.h与xx.cc),则二者属于同一组件单元,不受上述直接包含限制。


可行解决方案

方案1:利用GCC预编译宏实现编译期检查

在c.h中添加预编译检查逻辑,通过__INCLUDE_LEVEL__宏判断是否为直接包含(__INCLUDE_LEVEL__ == 1表示直接从源文件引入),配合CMake的编译选项控制允许直接包含的目标:

// c.h 修改后
#ifndef C_H_GUARD
#define C_H_GUARD

// 检查是否为源文件直接包含且未授权,同时排除同一组件单元情况
#if defined(__INCLUDE_LEVEL__) && __INCLUDE_LEVEL__ == 1 \
    && !defined(ALLOW_DIRECT_C_H) \
    && (!defined(__FILE_NAME__) || (__FILE_NAME__ != "c.cc"))
#error "禁止直接包含c.h,请通过b.h或a.h间接引入"
#endif

#include <string>
#endif

在CMake中,仅对允许直接包含c.h的目标添加编译定义:

# 允许c.cc直接包含c.h(同一组件单元)
target_compile_definitions(c_target PRIVATE ALLOW_DIRECT_C_H)

# 其他目标默认不添加,直接包含c.h会触发编译错误
target_compile_definitions(a_target PRIVATE)

方案2:CMake预编译脚本检查

编写脚本扫描源文件的#include语句,提前拦截违规的直接包含操作:

Python检查脚本(check_includes.py)

import sys
import re

# 定义禁止直接包含的头文件
FORBIDDEN_HEADERS = {"c.h"}
# 匹配#include "xx.h"的正则
INCLUDE_PATTERN = re.compile(r'^\s*#\s*include\s*"([^"]+)\.h"')

for filename in sys.argv[1:]:
    base_name = filename.split('/')[-1].rsplit('.', 1)[0]
    with open(filename, 'r', encoding='utf-8') as f:
        for line_num, line in enumerate(f, 1):
            match = INCLUDE_PATTERN.match(line)
            if match:
                header_base = match.group(1)
                # 同一组件单元,跳过检查
                if header_base == base_name:
                    continue
                # 检查是否是禁止直接包含的头文件
                if f"{header_base}.h" in FORBIDDEN_HEADERS:
                    print(f"错误:{filename}:{line_num} 直接包含了禁止的头文件,请通过间接方式引入")
                    sys.exit(1)

CMake配置

add_custom_target(
    check_includes
    COMMAND python3 ${CMAKE_SOURCE_DIR}/check_includes.py ${CMAKE_SOURCE_DIR}/a.cc
    COMMENT "扫描源文件直接包含违规头文件情况"
)

# 将检查任务绑定到目标编译前
add_dependencies(a_target check_includes)

方案3:完善GCC插件实现精准拦截

若坚持使用GCC插件,可通过PLUGIN_INCLUDE_FILE事件传递的include_file_info结构体,判断当前处理文件类型及被包含文件,触发错误提示:

简化插件代码(include_check_plugin.cc)

#include <gcc-plugin.h>
#include <plugin-version.h>
#include <tree.h>
#include <diagnostic.h>
#include <string.h>

int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) {
    if (!plugin_default_version_check(version, &gcc_version)) {
        error("插件版本与当前GCC版本不兼容");
        return 1;
    }
    register_callback(plugin_info->base_name, PLUGIN_INCLUDE_FILE,
                      (plugin_callback_func)check_direct_include, NULL);
    return 0;
}

void check_direct_include(void *data, struct include_file_info *info) {
    const char *current_file = info->current_file;
    const char *included_file = info->included_file;

    // 跳过空指针情况
    if (!current_file || !included_file) return;

    // 判断当前文件是.cc源文件,且直接包含了c.h
    if (strstr(current_file, ".cc") != NULL && strcmp(included_file, "c.h") == 0) {
        // 提取文件基础名称,判断是否为同一组件单元
        const char *current_base = strrchr(current_file, '/');
        current_base = current_base ? current_base + 1 : current_file;
        const char *included_base = strrchr(included_file, '/');
        included_base = included_base ? included_base + 1 : included_file;

        char current_prefix[256] = {0}, included_prefix[256] = {0};
        strncpy(current_prefix, current_base, strchr(current_base, '.') - current_base);
        strncpy(included_prefix, included_base, strchr(included_base, '.') - included_base);

        // 非同一组件单元则触发错误
        if (strcmp(current_prefix, included_prefix) != 0) {
            error_at(input_location, "禁止直接包含%qs,请通过间接依赖引入", included_file);
        }
    }
}

CMake编译插件并配置

# 编译GCC插件
add_library(include_check_plugin SHARED include_check_plugin.cc)
target_compile_options(include_check_plugin PRIVATE -fPIC -shared)

# 给目标添加插件编译选项
target_compile_options(a_target PRIVATE -fplugin=${CMAKE_BINARY_DIR}/libinclude_check_plugin.so)

内容的提问来源于stack exchange,提问作者xialin li

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 11:05:11