0
点赞
收藏
分享

微信扫一扫

手机图片怎么提取文字?高效渠道一览

捌柒陆壹 2023-06-02 阅读 100

【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 中介绍如下:

也就是没有声明的函数,自动使用隐式声明,创建一个声明,例如这个程序没有声明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

举报

相关推荐

0 条评论