vs中printf未定义标识符_一个printf(结构体指针)引发的血案

864435abefdcf78985a2ddcd366e58c4.png

这是道哥的第015篇原创

关注+星标公众号,不错过最新文章

一、前言

1. 为什么写这篇文章

在上周六,我在公众号里发了一篇文章:C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻,以直白的语言、一目了然的图片来解释指针的底层逻辑,有一位小伙伴对文中的代码进行测试,发现一个比较奇怪的问题。我把发来的测试代码进行验证,思考好久也无法解释为什么会出现那么奇怪的打印结果。

为了整理思路,我到阳台抽根烟。晚上的风很大,一根烟我抽了一半,风抽了一半,可能风也有自己的烦恼。后来一想,烟是我买的,为什么让风来抽?于是我就开始抽风!不对,开始回房间继续抽代码,我就不信,这么简单的 printf 语句,怎么就搞不定?!

于是就有了这篇文章。

2. 你能得到什么收获

  1. 函数参数的传递机制;
  2. 可变参数的实现原理(va_list);
  3. printf 函数的实现机制;
  4. 面对问题时的分析思路。

友情提醒:文章的前面大部分内容都是在记录思考问题、解决问题的思路,如果你对这个过程不感兴趣,可以直接跳到最后面的第四部分,用图片清晰的解释了可变参数的实现原理,看过一次之后,保管你能深刻记住。

3. 我的测试环境

3.1 操作系统

每个人的电脑环境都是不一样的,包括操作系统、编译器、编译器的版本,也许任何一个小差别都会导致一些奇奇怪怪的的现象。不过大部分人都是使用 Windows 系统下的 VS 集成开发环境,或者 Linux 下的 gcc 命令行窗口来测试。

我一般都是使用 Ubuntu16.04-64 系统来测试代码,本文中的所有代码都是在这个平台上测试的。如果你用 VS 开发环境中的 VC 编译器,可能在某些细节上与我的测试结果又出入,但是问题也不大,遇到问题再分析,毕竟解决问题也是提升自己能力的最快途径。

3.2 编译器

我使用的编译器是 Ubuntu16.04-64 系统自带的版本,显示如下:

d1422087786f81f26f4b4145d6bf4931.png

另外,我安装的是 64 位系统,为了编译 32 位的可执行程序,我在编译指令中添加了 -m 选项,编译指令如下:

gcc -m32 main.c -o main

使用 file main 命令来查一下编译得到的可执行文件:

16d584dec79001ff7cdae042b65d7394.png

所以,在测试时如果输出结果与预期有一些出入,先检查一下编译器。C 语言本质上都是一些标准,每家的编译器都是标准的实现者,只要结果满足标准即可,至于实现的过程、代码执行的效率就各显神通了。

二、问题导入

1. 网友求助代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct 
{
    int age;
    char name[8];
} Student;

int main()
{
    Student s[3] = {
    {1, "a"}, {2, "b"}, {3, "c"}};
    Student *p = &(s[0]);
    printf("%d, %d n", *s, *p);
}

2. 期望结果

根据上篇文章的讨论,我们知道:

  1. s 是一个包含 3 个元素数组,每个元素的类型是结构体 Student;
  2. p 是一个指针,它指向变量s,也就是说指针 p 中保存的是变量 s 的地址,因为数组名就表示该数组的首地址。

既然 s 也是一个地址,它也代表了这个数组中第一个元素的首地址。第一个元素类型是结构体,结构体中第一个变量是 int 型,因此 s 所代表的那个位置是一个 int 型数据,对应到示例代码中就是数字 1。因此 printf 语句中希望直接把这个地址处的数据当做一个 int 型数据打印出来,期望的打印结果是:1, 1

这样的分析过程好像是没有什么问题的。

3. 实际打印结果

我们来编译程序,输出警告信息:

11533db1ea4582135b9834be1f749cf7.png

警告信息说:printf 语句需要 int 型数据,但是传递了一个 Student 结构体类型,我们先不用理会这个警告,因为我们就是想通过指针来访问这个地址里的数据。

执行程序,看到实际打印结果是:1, 97,很遗憾,与我们的期望不一致!

三、分析问题的思路

1. 打印内存模型

可以从打印结果看,第一个输出的数字是 1,与预期符合;第二个输出 97,很明显是字符 'a' 的 ASCII 码值,但是 p 怎么会指到 name 变量的地址里呢?

首先确认 3 个事情:

  1. 结构体 Student 占据的内存大小是多少?
  2. 数组 s 里的内存么模型是怎样的?
  3. s 与 指针变量 p 的值是否正确?

把代码改为如下:

Student s[3] = {
    {1, "a"}, {2, "b"}, {3, "c"}};
Student *p = s;

printf("sizeof Student = %d nn", sizeof(Student));

printf("print each byte in s: ");
char *pTmp = p;
for (int i = 0; i < 3 * sizeof(Student); i++)
{
   if (0 == i % sizeof(Student))
        printf("n");
   printf("%x ", *(pTmp + i));
}
printf("nn");

printf("print value of s and p nn");
printf("s = 0x%x, p = 0x%x nn", s, p);

printf("%d, %d n", *s, *p);

我们先画一下数组 s 预期的内存模型,如下:

02387d39302750312776932de485f4fc.png
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值