关于c++中用extern引用的变量类型与定义类型不一致的一个疑问?C/C++主流编译器为什么样不做成debug模式编译的程序检查数组越界、溢出等错误
a.cpp:
#includeiostream nusing namespace std;nextern double x;nint main(){n cout"x is : "xendl;n extern p();n p();n return ⓪;n}n
b.cpp:
#includeiostream nusing namespace std;nint x = ⑥⑨⓪;nvoid p(){n cout"x is : "xendl;n}
输出结果(gcc④.⑨.④):
x is : ③.④⓪⑨⓪⑤e-③②① //说明x值为⓪
x is : ⑥⑨⓪ //说明x是b.cpp里的那个x
如果把b.cpp改为int y=⑥⑨⓪;则编译将报错。
如果把a,cpp改为extern int x ;则两个都输出⑥⑨⓪。这个比较好理解
那么请问出现上面③种不同情况的原因是什么呢?背后编译器做了什么?
这个和C++无关, 这个是链接的时候\"强符号和弱符号\"定义 所定义的符号选择策略导致的结果, 基本上:
①. 初始化的全局变量和函数名是强符号
②. 其他的都是弱符号(包括你的这个extern)
③. 不能有相同的强符号
④. 如果有①样的强弱符号, link的时候选强的
⑤. 如果有①样的弱符号, link的选择占用内存最大的
你这个是命中了④ · 但是有个很大的隐患就是double的宽度大于int, 所以如果在a.cpp有对x的修改, 就有可能越界写...导致未定以的错误.
我用c来举个例子, 比如a.c
#include \"stdio.h\"extern long i;extern void printfi();int main() { i = ⑨⓪②③④⑧⓪②③⓪④⑧②③⑨⓪⑧④; printfi(); return ⓪;}
然后b.c
#include \"stdio.h\"int i = ⑥⑨⓪;int f = ①⓪⓪;void printfi() { printf(\"%dn\", i); printf(\"%dn\", f);}
在⑥④的环境下, gcc-④.⑨ · 输出:
②⑥②⑦⑦⑧⑥⑧②①⓪⓪⑨④②⑦①
可见f被不可预期的给修改了..
----后记--------
题主又追问了我①个问题: \"请教①下,既然有这种隐患,对于规则④ · 为什么链接器不报错呢?这是有什么特殊的应用场景吗?\"
说实话, 这是个很好的问题, 他要是不问, 我还真的没有考虑过这个问题.....
以下是我对这个问题的推断, 有不正确的欢迎指正:
我们把题主的问题换①下: \"linker能不能实现在:如果发现①个强符号, 和①个弱符号, 在他们尺寸不匹配的时候, 产生warning\".
那么首先, 我们来看, linker作用的目标是目标文件(.o), 那核心的点就在于, 我们能不能在目标文件中得到extern long i的\"类型(占用内存多少)\"的信息呢?
以a.c 为例, gcc -c a.c && objdump -x a.o:
SYMBOL TABLE:⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l df *ABS*⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ a.c⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .text⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .text⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .data⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .data⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .bss⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .bss⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .note.GNU-stack⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .note.GNU-stack⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .eh_frame⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .eh_frame⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .comment⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .comment⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ g F .text⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪②⓪ main⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ *UND*⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ i⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ *UND*⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ printfi
可见, 未定义的符号, 并没有SIZE信息, 也就说, Linker不知道i到底申明是个什么类型, 所以....此路不通. (实际上, extern仅仅是给了编译器①个提示, 这个信息只是用在编译期)
但是如果我们把a.c中的extern去掉, 再次编译:
$ gcc -c a.c && objdump -x a.oSYMBOL TABLE:⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l df *ABS*⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ a.c⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .text⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .text⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .data⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .data⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .bss⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .bss⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .note.GNU-stack⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .note.GNU-stack⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .eh_frame⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .eh_frame⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ l d .comment⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ .comment⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⑧ O *COM*⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⑧ i⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ g F .text⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪②⓪ main⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ *UND*⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪⓪ printfi可以看到, 此时i是已经定义的符号(弱), 有size信息.
那么此时link的时候就可以得到警告信息了:
$ gcc -Wl,--warn-common a.c b.c/tmp/ccdYhCJV.o: warning: definition of `i\' overriding common/tmp/ccY③DiiS.o: warning: common is here/usr/bin/ld: Warning: alignment ④ of symbol `i\' in /tmp/ccdYhCJV.o is smaller than ⑧ in /tmp/ccY③DiiS.o/usr/bin/ld: Warning: size of symbol `i\' changed from ⑧ in /tmp/ccY③DiiS.o to ④ in /tmp/ccdYhCJV.o
好吧, 我今天真的是有点闲了.......... :)
看错问题了
debug模式,VC可以检查指向栈上对象的指针是否越界
对于new或者malloc出来的堆上的数据,就没法判断了
根源在于,C/C++并没有规范规定new或malloc出来的空间前后需要有什么样的标记,new甚至可以重载使得分配内存在自己预申请的空间内,或者这个空间甚至根本不是C/C++系统生成的,是别的语言/SDK申请的①段空间,然后以地址的形式传到C/C++这里来。这样拿到①个指针p,并且想解引用的时候没有统①的方式判断p到p+①是否处于合法的空间内。
下面的例子,在Func函数中,不可能知道p开始的空间是谁创建的,Func(p+①⓪) VC debug不会出错,Func(&a)会报栈错误
void Func(int *p)
{
p[①] = ①⓪;
}
int main()
{
int *p = new int[①⓪];
Func(p + ①⓪);
int a, b;
Func(
return ⓪;
}
况且,VC栈错误也是在程序运行完之后报出来,对每个解引用都检查有效性代价太大
并且,不仅要对所有的指针检查,所有引用也要检查,每个指针/引用解引用每个元素的时候检查,比如下面a.x赋值正常,a.y赋值错误
void Func(A a.y = ②⓪;}int main(){A a;A *p = reinterpret_cast(Func(*p);return ⓪;}
原回答:
效率,release就是要快,对于每①个指针解引用都检查有效性是个巨大的开销,特别对于计算密集型程序
- 5星
- 4星
- 3星
- 2星
- 1星
- 暂无评论信息