文章目录
先看一个例子:
#include <stdio.h>
int main(void) {
unsigned char arr[] = "0123456789abcdefghijk";
struct A {
int a;
char b;
char c;
char d;
int e;
} p, *pp;
struct B {
int a;
char b;
int c;
};
p.a = 1;
p.b = '2';
p.c = '3';
p.d = '4';
p.e = 5;
pp = &p;
printf("pp->a: %d\npp->b: %c\npp->c: %c\npp->d: %c\npp->e: %d\n", pp->a, pp->b, pp->c, pp->d, pp->e);
printf("**********\n");
pp = (struct A *)arr;
printf("pp->a: %d\npp->b: %c\npp->c: %c\npp->d: %c\npp->e: %d\n", pp->a, pp->b, pp->c, pp->d, pp->e);
return 0;
}
运行结果:
pp->a: 1
pp->b: 2
pp->c: 3
pp->d: 4
pp->e: 5
**********
pp->a: 858927408
pp->b: 4
pp->c: 5
pp->d: 6
pp->e: 1650538808
上述是一个将数组类型变量强制类型转换为 struct A 的例子,结合结构体内存分布的内容我们可以看出:结构体数据类型转换的本质就是对结构体内存空间的填充。通过这种方式,可以把某一起始地址的数据类型与结构体成员相对应。
结构体之间的强制类型转换要理解结构体之间的强制类型转换,需要明白以下几点原理:
- 结构体变量是如何分布内存的。
- 结构体变量的内存首地址。
- 结构体成员在结构体内存中的偏移地址。
实际上在上述的内容中,我们已经提到了这 3 点内容。
先看一个例子:
#include <stdio.h>
struct A {
int x;
char y;
} a, *pa;
struct B {
char x;
int y;
} b, *pb;
int main(void) {
a.x = 1;
a.y = 'A';
pa = &a;
printf("pa->x: %d, pa->y: %c\n", pa->x, pa->y);
b.x = 'A';
b.y = 1;
pb = &b;
printf("pb->x: %c, pb->y: %d\n", pb->x, pb->y);
struct B z;
z.x = ((struct B *)pa)->x;
printf("z.x: %c, z.y: %d\n", z.x, z.y);
}
输出结果:
pa->x: 1, pa->y: A
pb->x: A, pb->y: 1
z.x: , z.y: 32766
上述例子为结构体之间的强制类型转换,根据结构体内存分布的内容,并且我们暂不考虑内存对齐的话,我们知道:
- a 的内存分布为:前 4B,后 1B
- b 的内存分配为:前 1B,后 4B
当我们执行强制类型转换时,本质是就是 C 语言会对结构体变量 a 的空间,按照 struct B 的布局进行解释:也就是说,将 a 的第一个字节看成 struct B 的第一个成员,且按 ASCII 码处理数据,而将后面的 4B 看成 struct B 的第二个成员,并按补码格式解释数据。
需要注意的是,C 语言中的结构体强制类型转换本质是对指针进行转换,所以转换的对象必须为一个指针类型:
struct str1 a;
struct str2 b;
a = (struct str1)b; // this is wrong
a = ((struct str1)&b); // this is correct
通过数组强制类型转换为结构体以及结构体之间互相转换的内容,我们可以总结到:C 语言中结构体变量之间直接的赋值和转换本质是将右值的内存数据直接覆盖到左值所占用内存空间中,然后再根据 C 语言对这块内存的理解(类型定义)表达出来。
struct in_addr {
unsigned long a_addr;
}
struct sockaddr_in {
unsigned short sin_family; // 地址类型(2B)
unsigned short int sin_port; // 端口号(2B)
struct in_addr sin_addr; // IP 地址(4B)
unsigned char sin_zero[8]; // 填充空间(8B)
}
struct sockaddr {
unsigned short sa_family; // 地址类型(2B)
char sa_data[14]; // 协议地址(14B)
}
在实际的网络编程中,通常会先初始化 sockaddr_in,再将它强制转化成 sockaddr 来使用。这两个结构体,长度都为 16 字节,sockaddr_in.sin_family 的数据存入 sockaddr.sa_family,剩下的 14 个字节存入 sockaddr.sa_data,这样在各种操作中可以方便的处理端口号和 IP 地址。