【C语言笔记】【陷阱系列】 函数声明问题
陷阱系列内容。用于记录各式各样有陷阱的C语言情况☺。
陷阱代码
我们看下如下代码:
首先是一个func.c
文件,申请一段内存用于存放字符串,将字符串内容拷贝进去,最后将内存地址返回。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* func(void)
{
printf("entry\n");
char *str = malloc(6);
strcpy(str, "hello");
printf("str %p\n", str);
printf("exit\n");
return str;
}
然后是一个func.h
文件,内容如下:
char* func(void);
最后是test.c
文件的main
函数,通过func
函数获取一个字符串,然后打印出来。注意这里没有包含func.h
文件。
#include <stdio.h>
//#include "func.h"
int main(int argc, char* argv[])
{
char *str = func();
printf("str %p\n", str);
printf("str %s\n", (char *)str);
return 0;
}
接下来编译一下上述代码:
$ gcc -o test test.c func.c -Wall
发现在没有包含func.h
的情况下,还可以正常编译通过,但是有警告信息。
运行一下这个程序:
$ ./test
entry
str 0x7fffc87bf2b0
exit
str 0xffffffffc87bf2b0
Segmentation fault (core dumped)
发现这个程序会出现 Segmentation fault 段错误。从打印信息有entry
和exit
来看,程序确实正常调用了func
函数,但是内存地址错了,在func
函数中,申请的地址是0x7fffc87bf2b0
,而返回的地址是0xffffffffc87bf2b0
。
下面我们将test.c
文件的中//#include "func.h"
的屏蔽去掉,如下:
#include <stdio.h>
#include "func.h"
int main(int argc, char* argv[])
{
char *str = func();
printf("str %p\n", str);
printf("str %s\n", (char *)str);
return 0;
}
重新编译运行:
$ gcc -o test test.c func.c -Wall
$ ./test
entry
str 0x7fffc29a52b0
exit
str 0x7fffc29a52b0
str hello
可以看到结果是对的,打印出了hello
,不会段错误了,申请的内存地址也都正确了。
为什么没有包含func.h
也能正常调用func
函数?但是调用的结果返回值会出错?
说明
重新编译一下没有包含func.h
的代码, 查看其的警告信息:
$ gcc -o test test.c func.c -Wall
test.c: In function ‘main’:
test.c:13:17: warning: implicit declaration of function ‘func’ [-Wimplicit-function-declaration]
13 | char *str = func();
| ^~~~
test.c:13:17: warning: initialization of ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
可以看到了两条警告,第一条警告表示隐式声明函数func
,另一条警告表示初始化时将整数赋给指针,未作类型转换,也就是初始化一个char *
类型时却赋值一个int
类型的值,没有使用强制类型转换。
上面的这些测试,我都是使用64位的平台,更换了一个32位平台后发现,也会出现如上的警告,但是运行不会出错,没有段错误,申请的内存地址也都一致了。
# ./test
entry
str 0x88a3008
exit
str 0x88a3008
str hello
所以造成上述问题的原因是什么呢?还得从编译警告入手。
第一条警告提示隐式声明函数func
,那么什么是隐式声明呢?在 C89 的 3.3.2.2 Function calls 中介绍如下:
也就是没有声明的函数,自动使用隐式声明,创建一个声明,例如这个程序没有声明func
,就会自动声明一个func
如下:
extern int func();
可以看到,因为隐式声明的存在,所以链接时可以找到func
函数。但是隐式声明函数的返回值是一个int
类型的值,这也就是为什么会有第二条警告的原因,此时声明的func
函数的返回值是int
类型的值,所以会警告初始化一个char *
类型时却赋值一个int
类型的值。
由于使用的是 64 位的平台,所以指针的大小为 8 个字节,整型数的大小为 4 个字节,返回的指针地址只剩下了低 32 位。看之前的测试结果,返回的地址是0x7fffc87bf2b0
, 返回的是int
型,所以只剩下了低 32 位的0xc87bf2b0
,又由于赋值给了char *
,由于int
是有符号数,而0xc87bf2b0
的最高位为 1 ,所以类型提升时高位全部都会补1,也就变成了0xffffffffc87bf2b0
。
$ ./test
entry
str 0x7fffc87bf2b0
exit
str 0xffffffffc87bf2b0
Segmentation fault (core dumped)
因此得到的地址就是错误的,访问该地址会出错,导致段错误的出现。
上述也同时解释了为什么在 32 位平台上,编译同样会有警告,但是却不会段错误。因为在 32 位平台上,指针的大小和整型数的大小同为 4 个字节,类型转换后不会出现数据损失,因此没有出错。
值得注意的是,隐式声明在 C99 中已将其删除,在 C99 的 Foreword 的第 5 条中写了与上一版本相比的主要变化包括哪些。
其中有一条如下:
删除了隐式函数声明。
然后我们在 C99 的 6.5.2.2 Function calls 章节中也就看不到隐式函数声明了。
也就是说目前的C标准中已经没有隐式声明函数了,但是 GCC 默认还允许隐式函数声明功能,只会发出一个警告,所以我们使用GCC编译就有警告而不是错误。
[参考资料]
Implicit function declarations sometimes work in C?
The C89 Draft
ISO/IEC 9899:1999
ISO/IEC 9899:201x
Implicit function declarations and linkage
本文链接:https://blog.csdn.net/u012028275/article/details/130939259