补遗篇之命名空间污染

概念

    C标准规定,除非用static限定,否则全局变量与函数都作用于全局(见补遗篇static),也就是说一个模块中定义的函数与全局变量可在所有其他模块中被调用。这导致C的符号命名没有层次,不同模块间名字相互冲突的概率很高。学术的表述就是:C命名空间易被污染。

    比如,不同软件模块中定义了同名但不同实现的非static函数,由于它们都全局可见,编译器链接时无法正确区分和选择这些同名函数,这是C语言命名空间污染典型的问题。一些编译器在linking阶段报告重定义错误(redifined symbol error),这样还好,根据错误提示很容易找到问题。但有的编译器却在同名符号里随便选择一个,连接通过并得到最终可执行程序,对外可能只提示重定义警告。可如果链接通过,多少人会去注意警告呢?这实际导致了随机的结果,用了李逵结果正确,下次调了李鬼,杯具了。于是抓耳挠腮,为嘛呢?而且有时某些不相关改动会影响链接器的选择,这更容易把人引入歧途,鬼知道编译器什么时候看谁对眼啊!

实例描述

    在两个文件里定义同名函数,分别用gcc编成静态库a.ab.a,然后在test.c里调用:

/***a.c***/

    int libfun() {  return 2;  }

/***b.c***/

    int libfun() {  return 5;  }

/***test.c***/

    void main()

    {

      int c =libfun();

      printf(“c =%d\n”, c);

    }

    用gcc编译test.c,在makefile里同时包含ab两个库,如果把a.a放在前面,结果就是”c=2”,而b.a在前面,结果就变成”c=5”。当然这只是gcc的处理方法,其它编译器可能不一样。问题是对于模块开发者和集成者,这类问题事先似乎无法避免,事后也很难定位。

个人经历

    笔者就曾在这点上有过教训,至今记忆犹新。某次完成一个功能库后,发给分公司同事集成,结果同事反映结果时对时错:

笔者:我的模块没问题,我自己写的测试程序运行结果正确,肯定是你调用逻辑不对

同事:不可能,我的上层应用调用其他功能相同的第三方模块没问题,用你的模块就时好时坏

笔者:什么叫时好时坏,模块算法固定,之前正确就说明算法没问题,你改了什么导致出现问题?

同事开始耍赖:我哪记得,反正现在不正确

……吵了半天,实在没办法,我在库里接口函数里统统加上log,让同事重新编译运行抓log,不想过一会同事电话过来:就说是你的问题吧,你改什么了,这次结果对了

无比惊讶中:XX,什么乱七八糟,就是加了log输出而已

同事:不可能,你肯定是改了什么,再看看,这次结果是对的。

难道printf也能影响结果,这到底怎么回事呢?于是抓狂查找,一天下来,无果。第二天同事又来电,怯怯:好象不是log的原因,今天用带log库又出错了。

笔者火山爆发:我XXXX,昨天脑细胞死一半了,能不能别谎报军情,你又改什么了?

同事也很委屈:什么也没改呀,就重编了一下。

沟通半天,越说越糊涂,无奈,只好带上源码出差(两地两部门,代码权限有控制),问题依然时隐时现,无数次试验后,突然,

笔者:恩,我这个XXX函数这次怎么log没进来?外部调用前的log都有,怎么到里面跑飞了

同事:没进来,到哪去了?为啥之前的log有时能进来?

笔者一拍脑门:明白了,快,在整个项目里搜索这个XXX函数名

果真,在另一个其他同事写的功能模块里也有这个XXX函数名,但实现的功能有差别,回头仔细查看整个项目的编译连接信息,终于发现了一个XXX redifined warning。于是名字改掉,幽灵再也不出现了。

同事:真没想到,怎么会有这种问题,为什么编译器连接时不报错呢,一堆警告谁会注意!

笔者:还是你的问题,我事先怎么知道其他人也会用这个函数名呢,我自己测试好好的,集成多个模块的事当然要你把关。

同事:不能赖我呀,一开始结果是对的,后来莫名其妙出问题,谁知道往这方面想呢!再说,你们写函数干吗都用这种大路货名字,大家都用,很容易撞车呀。

最终责任认定无果。

    相信笔者不是唯一有类似体验的,这种问题时隐时现,而且容易错误联系,把不相干因素当作调查方向而浪费精力。不过“见怪不怪,其怪自败”,后来其他同事也碰到类似情况,建议他们到编译信息里查一下redefine关键字,果然很快解决问题。得意地享受完大家的崇拜眼光,不禁感慨:“还是经验最有价值!”。那能不能事先避免命名冲突问题,防止编译器乱点鸳鸯谱呢:)

解决方法

    命名冲突是C固有问题,自身范围内没有一劳永逸的解决办法(否则也不会有受争议的C++了),但一些局部防范的手段还是有的,包括:

    1)  static限制函数或变量作用域,一方面static可以限制自身作用域,防止被其他模块调用;同时C规定局部优于全局,本地文件中static量优于其他模块里同名的全局量被调用,或者说,两个同名符号,只要其中一个用static修饰,就可互不影响。

    2)  把模块名作为模块里所有非static函数的前缀,这可能很难完全做到,但起码一些冲突可能性很大的基础函数可以加上,比如add, sub, mux等名字,想不冲突都难啊。

    3)  从全局考虑,把一些公共函数提取出来,形成公共底层库,而不是在每个模块中都重复实现。比如某些常用的基础算法,字符操作等。

    可对照补遗staticC潜规则函数命名方式。

对应的interpositioning问题

    重名如果发生在用户自定义函数与C标准库之间,就是所谓interpositioningC规定,一旦实现出现与标准库同名的用户自定义函数,用户函数将取代该库函数的所有行为,注意:不仅取代用户代码里所有库函数调用,所有系统调用内部对原库函数的调用也会被代替。也就是说当编译器注意到库函数与用户定义函数同名,它认定用户函数优先级高于库函数。这是C的逻辑:程序员有最高权限,所做所为都是有原因、有意识的,错的也是对的。

    但谁也不可能记住标准库里所有函数名,有时无意间就覆盖了库函数。这种自定义函数误覆盖C标准库函数,其实是另一种形式的命名空间污染。尽管C本意是给程序员更多发挥空间,可多数人或许并不希望有这个空间,起码不应通过interpositioning这种隐含方式。
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值