结构体内部变量的寻址
在了解c语言结构体类型如何进行转换之前,我们需要清楚结构体是如何进行内部变量寻址的,我们先声明一个如下所示的结构体
struct st{
int a;
char b;
long long c;
int vec[100];
}
并且定义一个struct st的实体,假设这个结构体在内存中的首地址为0,那么这个实体在内存中的分布是这样紧凑分布的
内部变量类型 | 字节数 | 地址 |
int | 4字节 | 0 |
char | 1字节 | 4 |
long long | 8字节 | 5 |
int vec[100] | 400字节 | 13 |
我们在定义结构体实体变量的时候,编译器通过声明的结构体类型计算出这个结构体所需要的内存大小,以上面的表格为例,如果要定义一个struct st类型的实体变量sta,编译器会给我们分配一个413字节的连续内存,然后记录分配的首地址(假设为)0,如果我们要给结构体里的变量a赋值为1,可以使用语句sta.a = 1,那么想一下这个语句是怎样实现的呢?其实这都是完全取决于编译器,编译器在编译代码的时候就已经知道sta是个结构体变量,并且知道sta这个结构体变量的相对首地址,也知道a是这个结构体的int类型,因此它就能通过sta在内存中的首地址和int类型在结构体中的偏移量计算出sta.a在内存中的实际地址,由于int类型是结构体的第一个内容变量,因此偏移量为0,那么sta.a在内存中的实际地址就是0,知道了sta.a的内存地址和变量类型,自然就能够执行语句sta.a = 1;
如果是给long long类型的变量赋值,编译器就会将long long类型在结构体中的偏移量加上分配给结构体实体的首地址得到long long的内容变量在内存中的地址,例如给sta.c赋值,那么sta.c的地址就是 0(首地址) + 4(int类型字节) + 1(char类型字节)= 5。结构体内部变量的寻址都是依靠我们当初声明结构体的方式实现的,我们声明的时候就已经告诉了编译器每个变量的类型。
结构体类型转换
我们已经知道,结构体里的变量在内存中是紧凑的,并且结构体里的变量地址是通过结构体在内存中的首地址加上变量类型在结构体中的偏移字节得到的,首地址和偏移字节是确定一个结构体变量的两个必要元素,换句话说,只要知道了首地址,和此首地址对应的结构体类型,就可以确定一个确确实实的结构体实体变量,那么我们能不能更改一下首地址对应的结构体类型,让编译器通过此首地址对应到另外一个结构体呢?答案是肯定的,这也是我们今天讨论的主要内容,在C++和JAVA语言中,经常会用到对象的类型转换,其实就是通过更改内存首地址对应的结构体类型实现的,当我们定义了一个结构体变量之后,编译器会记录这个结构体变量的首地址,以及这个首地址对应的结构体类型,在编译之后会有一个专门的内存区域存放像这样的静态数据,在运行期间,如果需要访问结构体变量,首先cpu先得到这两个数据,然后才能对结构体实体变量操作。
如果我们将这两个数据拷贝一份,然后将其中拷贝的结构体类型更改为另一种结构体类型,那么就实现了结构体的类型转换, c语言如何通过结构体类型指针查找结构体的变量完全是由编译器负责的,如果已经声明了结构体,那么你可以将任何一处可以操作的内存地址转换成结构体类型,如下图所示,我们甚至可以将一个int类型的数据转换成结构体类型,不用觉得奇怪,这完全是合理的。
struct st{
int num;
};
int main(){
int a=100;
struct st* sta = (struct st*)(&a);
cout<<sta->num<<endl;
}
打印结果就是100,关键语句struct st* sta = (struct st*)(&a); 正是实现了将变量a的地址拷贝一份(如果你要问拷贝到了哪儿?应该是拷贝到了可执行程序的静态内存中),然后将拷贝的地址和struct st结构体类型相对应,这样执行sta->num的时候,cpu就会得知sta的值就是struct st类型变量的首地址,
再换另一个例子,我们顶一个两个结构体,一个是son,另一个是father,son是father的变量,根据之前了解到的信息,我们可以得知son的首地址和father的首地址其实都是变量c的首地址,只不过这个首地址对于son和对于father意味着什么,这是由编译器在编译的时候决定的,编译器会告诉cpu这个首地址对于son来说就是son结构体变量的首地址,而对于father来说就是father结构体变量的首地址。
struct son{
int c;
};
struct father{
struct son p;
};
int main()
{
struct father a1;
(a1.p).c=10;
cout<<(a1.p).c<<endl;
struct son* a2 = (struct son*)(&a1);
cout<<a2->c<<endl;
}
实际上道理是非常简单的,只要编译器告诉cpu哪儿个内存地址是哪儿个结构体变量的首地址,cpu就会傻傻呼呼的信以为真,这完全是编译器在编译过程中实现的!