由 printf(结构体) 引发的错误

一、问题引入

首先,测试环境如下:

下面是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

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

int main(void)
{
student s[3] = { {1, "a"}, {2, "b"}, {3, "c"} };
student *p = &(s[0]);

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

return 0;
}

这段代码非常简单,s 是一个包含 3 个元素的数组,每个元素的类型是结构体 student

p 是一个指针,它指向变量 s ,也就是说指针 p 中保存的是变量 s 的地址,因为数组名就表示该数组的首地址。

原本,期望结果应该是 1 1,因为 sp 都指向这个数组的首地址,也就是数组中第一个元素的地址。下面来看打印结果:

结果竟然是 1 0

二、问题分析

1、打印内存模型

下面先看一下结构体 s 的内存模型是否和我们所设想的一致,修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

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

int main(void)
{
student s[3] = { {1, "a"}, {2, "b"}, {3, "c"} };
student *p = &(s[0]);

printf("sizeof student = %d \n\n", 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("\n\n");

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

return 0;
}

结果如下:

从打印结果来看是没有任何问题的。

既然 sp 指向同一个地址,那为什么打印出来的结果会不对呢?

2、分开打印

既然第一个 *s 打印结果是正确的,那么就把这个两个数据分开来打印,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

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

int main(void)
{
student s[3] = { {1, "a"}, {2, "b"}, {3, "c"} };
student *p = &(s[0]);

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

return 0;
}

结果如下:

分开打印后 *p 的打印结果竟然就正确了。

但从这里看,大致可以判定为 printf 打印出第一个值后影响到了第二值的打印,为什么会出现这种情况呢?

三、进一步排查

既然知道了是 printf 打印时出现的问题,那么下面就继续来对这个问题进行分析。

1、打印两个字符串

1
2
3
char aa[] = "hello world";
char *p = aa;
printf("%d %d \n", *aa, *p);

编译运行,结果是 104,没有问题。

2、打印结构体

1
2
3
4
student s = { 1, "a"};

printf("%d \n", s);
printf("%d, %d \n", s, s);

结果又打印出了 0:

四、解决问题

最终再 StackOverflow 上找到了答案:use printf(“%s”,..) to print a struct, the struct’s first variable type is ‘char *’, why can get a right string stored in ‘char *’?

总的来说,用 printf 来打印结构体类型的变量是 undefined behavior,也就是未定义行为。这个时候,会发生什么就取决于编译器了

为了避免发生 undefined behaviour,可以在编译时加上 -Wall -Wextra -Werror,这样前面的代码编译时就会报错:


由 printf(结构体) 引发的错误
http://example.com/2024/12/26/由printf-结构体-引发的错误/
作者
Yu xin
发布于
2024年12月26日
许可协议