当前位置: 首页 > wzjs >正文

杭州 电子商务网站建设安徽省建设安全质量协会网站

杭州 电子商务网站建设,安徽省建设安全质量协会网站,网站是用什么做的吗,企业网站 三合一今天有个同事找到我,说发现了一个很神奇的问题,编译代码提示了未定义错误,但是明明包含了对应的头文件,而且查了好几遍,确认不存在包含错的问题,但就是编译失败,都怀疑是编译器 BUG 了&#xff…

今天有个同事找到我,说发现了一个很神奇的问题,编译代码提示了未定义错误,但是明明包含了对应的头文件,而且查了好几遍,确认不存在包含错的问题,但就是编译失败,都怀疑是编译器 BUG 了!

首先本着严谨的态度,不能说编译器不存在 BUG,但从实际角度来看,我们使用的编译器都是稳定发布版,而且用的都是最基本的功能,且未定义错误算是最基本的错误类型了,这种情况下出 BUG 的概率可以说微乎其微,所以还是让同事打开他的代码看一看。

不看不知道,一看 … 就很明了了!实际上这个问题很典型,很多老工程师在开发一些比较复杂的项目时如果稍不留神也会出现这种问题,不过只要你了解了问题的原因,下次再出现时就能很快定位到问题点并解决。

我们今天就来看看这到底是个啥问题!

首先我们准备几个代码文件,分别为 a.ca.hb.h

/** * a.c* 
**/#include <stdio.h>
#include "a.h"
#include "b.h"int main(void) {File1Struct file1;File2Struct file2;file1.data = 100;file1.ptr = &file2;  // 使用 b.h 中的结构体file2.ptr = NULL;    // 初始化指针printf("File1 data: %d\n", file1.data);return 0;
}
/** * a.h* 
**/#ifndef A_H
#define A_H#include "b.h"typedef struct _File1Struct {int data;File2Struct *ptr;
} File1Struct;#endif  // A_H
/** * b.h* 
**/#ifndef B_H
#define B_H#include "a.h"typedef struct _File2Struct {int value;File1Struct *ptr; 
} File2Struct;#endif  // B_H

代码很简单,我们也不急着分析,先编译一下看看:

jay@jaylinuxlenovo:~/test/code$ gcc a.c -o app
In file included from a.h:9,from a.c:7:
b.h:13:5: error: unknown type name ‘File1Struct’13 |     File1Struct *ptr;|     ^~~~~~~~~~~

可以看到编译报错了,报错位置是 b.h 的第 13 行,也就是下面这条代码:

File1Struct *ptr; 

而这条代码是包含在结构体 File2Struct 中的一个成员:

typedef struct _File2Struct {int value;File1Struct *ptr; 
} File2Struct;

错误信息的含义是 “未知的类型名 File1Struct”,根据一开始给出的代码文件,我们知道 File1Struct 是定义在 a.h 中的一个结构体:

typedef struct _File1Struct {int data;File2Struct *ptr;
} File1Struct;

所以第一个想到的就是没有包含 a.h 头文件,然而我们再回看 b.h 这个文件,明明是在开头就包含了 a.h:

/** * b.h* 
**/#ifndef B_H
#define B_H#include "a.h" //<----  这里明明包含了 a.htypedef struct _File2Struct {int value;File1Struct *ptr; 
} File2Struct;#endif  // B_H

明明包含了头文件,但却找不到头文件所包含头文件中定义的类型!你说奇不奇怪?如果你还没摸清其中的道道,那觉得奇怪是必然的,甚至有些朋友在自己的项目中遇到某个文件突然报这个错时会觉得其他的文件不会出现这种问题,就这个文件会出现这种问题,难道是玄学?

当然不是,一点都不玄,甚至可以说这是个非常低级的错误!

如果仔细观察 a.hb.h 这两个头文件,就会发现一个很别扭的现象,即 a.h 中定义的结构体里面其中一个成员是 b.h 中定义的结构体,而 b.h 定义的这个结构体中又包含了 a.h 中定义的结构体,导致 a.h 包含了 b.h,而 b.h 又包含了 a.h !这就是典型的循环依赖!

我们知道在编译的 预处理 阶段,头文件中的内容会被直接插入到引用它的源文件中,此时就有意思了,a.h 中的内容会被插入到源文件,但 a.h 中又包含了 b.h,此时 b.h 的内容同样也会被插入,而 b.h 又包含了 a.h,这时候又开始插入 a.h 的内容,如此循环,无穷无尽,整个编译过程就会在预处理阶段进入死循环。

当然我们在编写头文件的时候都有个规则,就是要用如下代码包裹实际内容:

#ifndef XXX
#define XXX// 文件内容#endif  

这段代码就是用于避免循环依赖进入死循环情况的,其原理也很简单,就是告诉预处理器在插入过 XXX 后如果遇到同样的内容就不再插入,这样在插入了 a.hb.h 后就不会再插入 a.h 了。然而这个问题解决了,我们的主要问题就很显现出来了。此时按照这个逻辑,在预处理后我们的 a.c 就会变成下面这样(实际会有更多内容,这里只保留我们关心的):

/** * a.c* 
**/// 省略 stdio.h 的内容 ......typedef struct _File2Struct {int value;File1Struct *ptr; 
} File2Struct;typedef struct _File1Struct {int data;File2Struct *ptr;
} File1Struct;int main(void) {File1Struct file1;File2Struct file2;file1.data = 100;file1.ptr = &file2;  // 使用 b.h 中的结构体file2.ptr = NULL;    // 初始化指针printf("File1 data: %d\n", file1.data);return 0;
}

很明显,File2Struct 定义在了 File1Struct 之前,自然在编译器解析 File2Struct 的成员类型 File1Struct 时它就是没有被定义过,当然这个与包含头文件的顺序有关,不同的顺序也会导致出现 File1Struct 定义在 File2Struct 之前的情况,此时就会报 File2Struct 未定义错误。

当把文件内容展开时这个问题变得显而易见,但大多数时候这种问题都是在一些比较复杂的工程结构下,动辄几十甚至上百的文件,且刚编写的时候大概率不会有问题,但往往在后期维护过程中修修补补,由于需求的增加需要在某个模块中加入另一个模块的接口,此时一不留神加了个会导致循环依赖的头文件,进而出现编译报警,此时所有文件自身内容实际都是没问题的,如果不特别留神头文件的包含关系,你就很难发现错误的原因!

问题找到了,怎么解决?

1 抽取公共内容到一个公共头文件

这种方法很好理解,既然 a 要用到 b 的内容,b 要用到 a 的内容,那我就搞一个 c,把 a 和 b 都需要的内容写到 c 中,然后 a 和 b 都包含 c 就行了,这种方式从源头就杜绝了循环依赖的出现,优化了代码的结构设计,也是最推荐的方式。

那如果我就是想要 a 包含 b,b 包含 a,还想要能编译成功,可行吗?

也可行!

2 使用前向声明

前向声明(Forward Declaration)是指在代码中仅声明某个类型或结构的存在,而不提供完整的定义。它允许代码引用某个类型(如结构体、类或函数),但推迟对其具体内容的定义。

基于这种方式,编译器就能在没有获取到某个类型的具体定义情况下先不进行报错,只要后续能找到具体的定义,编译就能成功:

/*** b.h***/#ifndef B_H
#define B_H#include "a.h"typedef struct _File1Struct File1Struct;  // 前向声明typedef struct _File2Struct {int value;File1Struct *ptr;
} File2Struct;#endif  // B_H

此时我们再尝试编译:

jay@jaylinuxlenovo:~/test/code$ make
gcc -c a.c
gcc -o app a.o 
jay@jaylinuxlenovo:~/test/code$ ./app 
File1 data: 100
jay@jaylinuxlenovo:~/test/code$ 

可以看到编译没有错误,最终程序也能正常运行,问题得到了解决。

最后还是要强调一下,虽然前向声明可以解决我们的问题,也可以精简代码与文件量,但一个稳定的系统一定是从设计层面就避免了类似的情况发生,而不是在发生后再通过某种方式进行补救。

http://www.dtcms.com/wzjs/790299.html

相关文章:

  • 福建漳州网站建设哪家便宜桂林百度网站建设
  • 凡科网站怎么做链接头像logo启动 wordpress 博客
  • 合肥网站建设培训中心广州无线电集团有限公司
  • 目前网站建设用哪种语言内蒙e登记官网最新
  • h5游戏网站wordpress调用侧边栏
  • 网站怎么做公司好看的模板图片
  • 住房和城乡建设部网站安全月网站插件开发
  • 手机网站模板cms郑州防控升级
  • 用源码建设网站抖音seo关键词优化
  • 网站关键词怎样修改网页模版之家
  • 如何建设手机版网站网站建设要什么软件
  • 安康做网站电话电子商务主要学什么就业方向及前景
  • 网站建设的公司实习做什么找关键词
  • 企业建站程序哪个好柯林建站程序
  • 网站 域名 授权服务器 分布式云南网站备案查询
  • 海拉尔网站建设sjteam企业管理咨询论文
  • 电子商务网站建设心得在百度上做网站推广怎么弄
  • wordpress post id搜索引擎seo外包
  • 做网站用python好吗wordpress可以用织梦模板吗
  • 成都网站制作软件番禺厂家关键词优化
  • 保定做公司网站的长沙购物网站建设
  • 做网站代理需要办什么执照成都设计公司怎么选
  • 餐饮企业网站开发背景qq关键词排名优化
  • 免费软件库合集软件资料网站网站建设管理情况自查报告
  • 汉中网站建设电话域名禁止网站相关
  • 网站建设ppt浙江专业网站建设商城报价
  • 月子会所网站建设方案网站与网页之间的区别是什么
  • 济南网站建设(选聚搜网络)电脑建设银行怎样设置网站查询
  • 百度网站排名优化价格在网上那里能接单做网站
  • 做网站的就业前景物语系列短篇资源WordPress