【C语言笔记】【陷阱系列】 函数声明问题

【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 段错误。从打印信息有entryexit来看,程序确实正常调用了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 中介绍如下:

If the expression that precedes the parenthesized argument list in a function call consists solely of an identifier, and if no declaration is visible for this identifier, the identifier is implicitly declared exactly as if, in the innermost block containing the function call, the declaration

        extern int  identifier();

appeared.

也就是没有声明的函数,自动使用隐式声明,创建一个声明,例如这个程序没有声明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 条中写了与上一版本相比的主要变化包括哪些。

This second edition cancels and replaces the first edition, ISO/IEC 9899:1990, as amended and corrected by ISO/IEC 9899/COR1:1994, ISO/IEC 9899/AMD1:1995, and ISO/IEC 9899/COR2:1996. Major changes from the previous edition include:

其中有一条如下:

remove implicit function declaration

删除了隐式函数声明。

然后我们在 C99 的 6.5.2.2 Function calls 章节中也就看不到隐式函数声明了。

If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined

也就是说目前的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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值