C 将在'foo';这里总是非零?
我有以下代码:C 将在'foo';这里总是非零?,c,C,我有以下代码: #include <stdio.h> #include <stdlib.h> extern void foo(double); int main(void) { // printf("address is: %p\n", *foo); if (foo) puts("indeed"); else puts("not"); exit(0); } 但是,如果第7行未注释,则不会编译: /tm
#include <stdio.h>
#include <stdlib.h>
extern void foo(double);
int main(void) {
// printf("address is: %p\n", *foo);
if (foo)
puts("indeed");
else
puts("not");
exit(0);
}
但是,如果第7行未注释,则不会编译:
/tmp/ccWvhcze.o: In function `main':
post.c:(.text.startup+0x5): undefined reference to `foo'
collect2: error: ld returned 1 exit status
那个么,若根据gcc,地址总是非零,为什么链接会失败呢?当然,我会期待它,因为这里没有定义
foo
,但为什么gcc声称地址总是非零?C标准是否要求翻译单元中的所有标识符始终计算为true?测试if(指针表达式)
检查指针是否为空指针。每个有效的对象和函数都保证不是空指针。(请注意,空指针不一定是“零”。测试if(指针表达式)
检查指针是否为空指针。每个有效的对象和函数都保证不是空指针。(请注意,空指针不一定是“零”。您必须记住,编译和链接对于C程序来说是两个完全独立的阶段。编译器说“如果(且仅当)链接并执行此代码,foo
将始终为非零”。但是,您接着尝试将该对象文件链接到可执行文件中,而不提供foo
的定义(仅提供声明)。这是非法的,所以链接失败foo
不是NULL或非NULL,它没有值。您必须记住,编译和链接对于C程序来说是两个完全独立的阶段。编译器说“如果(且仅当)链接并执行此代码,foo
将始终为非零”。但是,您接着尝试将该对象文件链接到可执行文件中,而不提供foo
的定义(仅提供声明)。这是非法的,所以链接失败foo
不为NULL或不为NULL,它没有值。未测试,因为我是在智能手机上编写的,我想foo
可能会变为零
例如,如果我的想法是正确的,那么与这个NASM代码链接将使foo
为零
bits 32
absolute 0
global foo
global _foo
foo:
_foo:
更新:
我用GCC4.8.1和NASM 2.11.08测试了这段代码,并得到了输出
address is: 00000000
indeed
此外,尽管此代码与原始代码不同,但此代码在Ubuntu、GCC 4.6.3上运行于Vagrant VM上
#include "types.h"
#include "user.h"
void foo(double a) {
(void)a;
}
int main(void) {
printf(1, "address is : %p\n", (void*)*foo);
if (foo)
printf(1, "indeed\n");
else
printf(1, "not\n");
exit();
}
发射输出
address is : 0
indeed
(从xv6Makefile
中的CFLAGS
中删除-Werror
,否则会出现编译错误)
还请注意,
*foo
成为函数foo
的地址,因为foo
作为运算符*
的操作数被转换为指向函数foo
的指针,然后用*
解除引用,成为函数foo
,然后再次转换为函数参数的指针。未测试,因为我是从智能手机编写的,我猜foo
可能会变为零
例如,如果我的想法是正确的,那么与这个NASM代码链接将使foo
为零
bits 32
absolute 0
global foo
global _foo
foo:
_foo:
更新:
我用GCC4.8.1和NASM 2.11.08测试了这段代码,并得到了输出
address is: 00000000
indeed
此外,尽管此代码与原始代码不同,但此代码在Ubuntu、GCC 4.6.3上运行于Vagrant VM上
#include "types.h"
#include "user.h"
void foo(double a) {
(void)a;
}
int main(void) {
printf(1, "address is : %p\n", (void*)*foo);
if (foo)
printf(1, "indeed\n");
else
printf(1, "not\n");
exit();
}
发射输出
address is : 0
indeed
(从xv6Makefile
中的CFLAGS
中删除-Werror
,否则会出现编译错误)
还请注意,
*foo
成为函数foo
的地址,因为foo
作为运算符*
的操作数被转换为指向函数foo
的指针,然后用*
解除引用,成为函数foo
,然后再次转换为函数参数的指针。编译器将删除if()
中的测试,因为它保证为true。因此,链接器无需解决任何问题。添加foo
的printf
会增加解析foo的需要。编译器会删除if()
中的测试,因为它保证为真。因此,链接器无需解决任何问题。添加foo
的printf
又增加了解决foo的需要。正如其他人所指出的,GCC假设“foo”永远不会为零。如果您有函数“foo”,那么它将有一些地址,如果没有定义函数foo,那么您的程序将无法链接,并且将没有可执行文件可运行。如果您正在链接到在链接时具有函数“foo”的“dll”或“so”文件,但后来在执行时它被删除,则您的可执行文件将不会加载。因此GCC安全地假设“foo”永远不会为零
然而,它在99%(99%)的情况下是正确的。正如MikeCat指出的,函数foo可能位于地址0处。对于用户空间的可执行文件,这种情况永远不会发生,甚至内核代码也不会采用这种技巧。将对象置于绝对地址的唯一实用程序是引导加载程序和其他直接在硬件上运行的程序
它们依靠链接器脚本将对象放置在正确的地址。这里有一个代码的例子,它在技术上是这样做的,但这是非常恶心的事情。我所知道的唯一需要使用这些技巧的C程序是引导加载程序代码
但是,即使引导加载程序可以做这样的事情,GCC仍然可能是正确的。大多数CPU或操作系统出于某些特殊目的保留地址“0”。大多数情况下,中断向量表位于该位置,在其他情况下,操作系统或体系结构本身为空指针保留地址0,因此任何访问该地址的尝试都将导致异常。我相信x86保护模式就是这样一种体系结构,它是所有流行操作系统(Linux、Windows)运行用户程序的模式
因此,在您的例子中,GCC在优化if语句方面可能是正确的,因为您使用的是x86版本的GCC。如果您找到了地址所在的体系结构