C语言数据结构笔记2:结构体地址的遍历_结构体嵌套
目录
问题提出:
结构体地址对其_内存布局:
获取结构体的基地址_结构体元素相较于基地址的偏移值:
通过结构体元素相较于基地址的偏移,逐个打印其值:
将虚拟地址与实际的元素地址联系起来:
TIP:
跳跃取址:
问题提出:
下方代码中有俩个结构体,它们包含俩种类型的数据,然后每个成员又对应一个地址
比如ABC_regs1结构体 我预定的虚拟地址范围是 0x03e8 - 0x03f1
那么其成员A1对于0x03e8,B1对应0x03e9,C1对应,D1对应0x03ea,E1对应0x03eb,F1对应0x03ec,G1对应0x03ed
对于ABC_regs2结构体 我预定的虚拟地址范围是 0x177B - 0x1781
那么其成员A1对于0x177b,B1对应0x177c,C1对应,D1对应0x177d,E1对应0x177e,F1对应0x177f,G1对应0x1780
假设现在给出一个地址0x03ec
我该如何类似于遍历数组一样从0x03e8开始,一点一点遍历到它的值
#include <stdio.h>typedef unsigned short int uint16_t; typedef signed short int int16_t;//0x03e8 - 0x03f1 typedef struct ABC_regs1 {uint16_t A1;uint16_t B1;int16_t C1;int16_t D1;uint16_t E1;int16_t F1;int16_t G1; }ABC_regs_1;//0x177B - 0x1781 typedef struct ABC_regs2 {uint16_t A2;uint16_t B2;int16_t C2;int16_t D2;uint16_t E2;int16_t F2;int16_t G2; }ABC_regs_2;typedef struct Letter_regs {ABC_regs_1 ABC1;//0x03e8 - 0x03f1ABC_regs_2 ABC2;//0x177B - 0x1781 }letter_regs;
结构体地址对其_内存布局:
为了方便输出测试,这里填充一些结构体的值进去,并输出结构体内存布局
这里的offset是虚拟的,不是真实物理地址:
通过这个历程了解结构体中元素是怎么存储的。
#include <stdio.h>#pragma pack(push, 1) //:将结构体的对齐方式设置为 1 字节,并将当前对齐设置保存到堆栈中。 //#pragma pack(pop):恢复之前保存的对齐设置。 //使用 #pragma pack 可以确保结构体成员之间没有填充字节,从而精确控制内存布局。typedef unsigned short int uint16_t; typedef signed short int int16_t;//0x03e8 - 0x03f1 typedef struct ABC_regs1 {uint16_t A1;uint16_t B1;int16_t C1;int16_t D1;uint16_t E1;int16_t F1;int16_t G1; }ABC_regs_1;//0x177B - 0x1781 typedef struct ABC_regs2 {uint16_t A2;uint16_t B2;int16_t C2;int16_t D2;uint16_t E2;int16_t F2;int16_t G2; }ABC_regs_2;typedef struct Letter_regs {ABC_regs_1 ABC1;//0x03e8 - 0x03f1ABC_regs_2 ABC2;//0x177B - 0x1781 }letter_regs;void print_struct_values(const letter_regs* regs) {const unsigned char* ptr = (const unsigned char*)regs;size_t struct_size = sizeof(letter_regs);printf("Memory layout of letter_regs:\n");for (size_t i = 0; i < struct_size; i++) {printf("Offset 0x%04lx: 0x%02x\n", i, ptr[i]);} }int main(void) {letter_regs my_regs;// 填充结构体my_regs.ABC1.A1 = 0x1234;my_regs.ABC1.B1 = 0x5678;my_regs.ABC1.C1 = -1234;my_regs.ABC1.D1 = -5678;my_regs.ABC1.E1 = 0x9ABC;my_regs.ABC1.F1 = -3456;my_regs.ABC1.G1 = -7890;my_regs.ABC2.A2 = 0xDEF0;my_regs.ABC2.B2 = 0x1357;my_regs.ABC2.C2 = -9012;my_regs.ABC2.D2 = -3456;my_regs.ABC2.E2 = 0x789A;my_regs.ABC2.F2 = -5678;my_regs.ABC2.G2 = -9012;// 打印结构体的内存布局print_struct_values(&my_regs); }
获取结构体的基地址_结构体元素相较于基地址的偏移值:
%zu
是 C 语言中用于格式化输出size_t
类型数据的格式说明符。
size_t
是一个无符号整数类型,通常用于表示对象的大小或数组的索引。在标准 C 库中,许多函数返回
size_t
类型的值,例如sizeof
运算符的结果和strlen
函数的返回值。#include <stdio.h> #include <stddef.h> // 包含 offsetof 宏#pragma pack(push, 1) //:将结构体的对齐方式设置为 1 字节,并将当前对齐设置保存到堆栈中。 //#pragma pack(pop):恢复之前保存的对齐设置。 //使用 #pragma pack 可以确保结构体成员之间没有填充字节,从而精确控制内存布局。typedef unsigned short int uint16_t; typedef signed short int int16_t;//0x03e8 - 0x03f1 typedef struct ABC_regs1 {uint16_t A1;uint16_t B1;int16_t C1;int16_t D1;uint16_t E1;int16_t F1;int16_t G1; }ABC_regs_1;//0x177B - 0x1781 typedef struct ABC_regs2 {uint16_t A2;uint16_t B2;int16_t C2;int16_t D2;uint16_t E2;int16_t F2;int16_t G2; }ABC_regs_2;typedef struct Letter_regs {ABC_regs_1 ABC1;//0x03e8 - 0x03f1ABC_regs_2 ABC2;//0x177B - 0x1781 }letter_regs;void print_struct_values(const letter_regs* regs) {const unsigned char* ptr = (const unsigned char*)regs;size_t struct_size = sizeof(letter_regs);printf("Memory layout of letter_regs:\n");for (size_t i = 0; i < struct_size; i++) {printf("Offset 0x%04lx: 0x%02x\n", i, ptr[i]);} }int main(void) {letter_regs my_regs;size_t BASE_ADDRESS = (void*)&my_regs;// 填充结构体my_regs.ABC1.A1 = 0x1234;my_regs.ABC1.B1 = 0x5678;my_regs.ABC1.C1 = -1234;my_regs.ABC1.D1 = -5678;my_regs.ABC1.E1 = 0x9ABC;my_regs.ABC1.F1 = -3456;my_regs.ABC1.G1 = -7890;my_regs.ABC2.A2 = 0xDEF0;my_regs.ABC2.B2 = 0x1357;my_regs.ABC2.C2 = -9012;my_regs.ABC2.D2 = -3456;my_regs.ABC2.E2 = 0x789A;my_regs.ABC2.F2 = -5678;my_regs.ABC2.G2 = -9012;// 打印结构体的内存布局//print_struct_values(&my_regs);printf("Base address of my_regs: %p\n", (void*)&my_regs);// 打印每个成员相对于结构体起始地址的偏移量printf("Offset of ABC1.A1: %zu\n", offsetof(letter_regs, ABC1.A1));printf("Offset of ABC1.B1: %zu\n", offsetof(letter_regs, ABC1.B1));printf("Offset of ABC1.C1: %zu\n", offsetof(letter_regs, ABC1.C1));printf("Offset of ABC1.D1: %zu\n", offsetof(letter_regs, ABC1.D1));printf("Offset of ABC1.E1: %zu\n", offsetof(letter_regs, ABC1.E1));printf("Offset of ABC1.F1: %zu\n", offsetof(letter_regs, ABC1.F1));printf("Offset of ABC1.G1: %zu\n", offsetof(letter_regs, ABC1.G1));printf("Offset of ABC2.A2: %zu\n", offsetof(letter_regs, ABC2.A2));printf("Offset of ABC2.B2: %zu\n", offsetof(letter_regs, ABC2.B2));printf("Offset of ABC2.C2: %zu\n", offsetof(letter_regs, ABC2.C2));printf("Offset of ABC2.D2: %zu\n", offsetof(letter_regs, ABC2.D2));printf("Offset of ABC2.E2: %zu\n", offsetof(letter_regs, ABC2.E2));printf("Offset of ABC2.F2: %zu\n", offsetof(letter_regs, ABC2.F2));printf("Offset of ABC2.G2: %zu\n", offsetof(letter_regs, ABC2.G2));}
通过结构体元素相较于基地址的偏移,逐个打印其值:
#include <stdio.h> #include <stdint.h> // 用于标准整数类型定义#pragma pack(push, 1) //:将结构体的对齐方式设置为 1 字节,并将当前对齐设置保存到堆栈中。 //#pragma pack(pop):恢复之前保存的对齐设置。 //使用 #pragma pack 可以确保结构体成员之间没有填充字节,从而精确控制内存布局。typedef unsigned short int uint16_t; typedef signed short int int16_t;//0x03e8 - 0x03f1 typedef struct ABC_regs1 {uint16_t A1;uint16_t B1;int16_t C1;int16_t D1;uint16_t E1;int16_t F1;int16_t G1; }ABC_regs_1;//0x177B - 0x1781 typedef struct ABC_regs2 {uint16_t A2;uint16_t B2;int16_t C2;int16_t D2;uint16_t E2;int16_t F2;int16_t G2; }ABC_regs_2;typedef struct Letter_regs {ABC_regs_1 ABC1;//0x03e8 - 0x03f1ABC_regs_2 ABC2;//0x177B - 0x1781 }letter_regs;void print_memory_at_offset(const void* base_address, size_t offset) {const unsigned char* ptr = (const unsigned char*)base_address;if (offset < sizeof(letter_regs)) {// 判断数据类型并打印if (offset % sizeof(uint16_t) == 0) // 假设偏移量对齐到 uint16_t{uint16_t value = *(const uint16_t*)(ptr + offset);printf("Value at offset 0x%04zx: 0x%04x\n", offset, value);} else if (offset % sizeof(int16_t) == 0) // 假设偏移量对齐到 int16_t{int16_t value = *(const int16_t*)(ptr + offset);printf("Value at offset 0x%04zx: %d\n", offset, value);} else // 默认按字节处理{unsigned char value = ptr[offset];printf("Value at offset 0x%04zx: 0x%02x\n", offset, value);}} else {printf("Offset 0x%04zx is out of bounds.\n", offset);} }int main(void) {letter_regs my_regs;// 填充结构体my_regs.ABC1.A1 = 0x1234;my_regs.ABC1.B1 = 0x5678;my_regs.ABC1.C1 = -1234;my_regs.ABC1.D1 = -5678;my_regs.ABC1.E1 = 0x9ABC;my_regs.ABC1.F1 = -3456;my_regs.ABC1.G1 = -7890;my_regs.ABC2.A2 = 0xDEF0;my_regs.ABC2.B2 = 0x1357;my_regs.ABC2.C2 = -9012;my_regs.ABC2.D2 = -3456;my_regs.ABC2.E2 = 0x789A;my_regs.ABC2.F2 = -5678;my_regs.ABC2.G2 = -9012;// 打印结构体的基地址printf("Base address of my_regs: %p\n", (void*)&my_regs);// 打印特定偏移量的数据print_memory_at_offset(&my_regs, 0x00); // 打印 A1 的值print_memory_at_offset(&my_regs, 0x02); // 打印 B1 的值print_memory_at_offset(&my_regs, 0x04); // 打印 C1 的值print_memory_at_offset(&my_regs, 0x06); // 打印 D1 的值print_memory_at_offset(&my_regs, 0x08); // 打印 E1 的值print_memory_at_offset(&my_regs, 0x0A); // 打印 F1 的值print_memory_at_offset(&my_regs, 0x0C); // 打印 G1 的值print_memory_at_offset(&my_regs, 0x0E); // 打印 A2 的值}
将虚拟地址与实际的元素地址联系起来:
接下来就能在应用层将这个虚拟地址与实际的联系起来了:
第二区结构体的基地址需要减去上个结构体所有成员的个数,否则还是上个结构体成员的基地址偏移量。这个可以自己去掉之后打印对比一下输出结果就知道了。
#include <stdio.h> #include <stdint.h> // 用于标准整数类型定义#pragma pack(push, 1) //:将结构体的对齐方式设置为 1 字节,并将当前对齐设置保存到堆栈中。 //#pragma pack(pop):恢复之前保存的对齐设置。 //使用 #pragma pack 可以确保结构体成员之间没有填充字节,从而精确控制内存布局。typedef unsigned short int uint16_t; typedef signed short int int16_t;//0x03e8 - 0x03ee typedef struct ABC_regs1 {uint16_t A1;uint16_t B1;int16_t C1;int16_t D1;uint16_t E1;int16_t F1;int16_t G1; }ABC_regs_1;//0x177B - 0x1781 typedef struct ABC_regs2 {uint16_t A2;uint16_t B2;int16_t C2;int16_t D2;uint16_t E2;int16_t F2;int16_t G2; }ABC_regs_2;typedef struct Letter_regs {ABC_regs_1 ABC1;//0x03e8 - 0x03f1 //7成员 14字节 //偏移量 0x00- 0x0cABC_regs_2 ABC2;//0x177B - 0x1781 //7成员 14字节 }letter_regs;#pragma pack(pop) //:恢复之前保存的对齐设置。void print_value_at_virtual_address(unsigned char * base_address, size_t virtual_address, size_t virtual_address_start) {const unsigned char* ptr = (const unsigned char*)base_address;size_t offset = (virtual_address - virtual_address_start)*2; // 计算偏移量if (offset < sizeof(letter_regs)) {// 判断数据类型并打印if (offset % sizeof(uint16_t) == 0) {uint16_t value = *(const uint16_t*)(ptr + offset);printf("Value at virtual address 0x%04zx: 0x%04x\n", virtual_address, value);} else if (offset % sizeof(int16_t) == 0) {int16_t value = *(const int16_t*)(ptr + offset);printf("Value at virtual address 0x%04zx: %d\n", virtual_address, value);} else {unsigned char value = ptr[offset];printf("Value at virtual address 0x%04zx: 0x%02x\n", virtual_address, value);}} else {printf("Virtual address 0x%04zx is out of bounds.\n", virtual_address);} }int main(void) {letter_regs my_regs;// 填充结构体my_regs.ABC1.A1 = 0x1234;my_regs.ABC1.B1 = 0x5678;my_regs.ABC1.C1 = -1234;my_regs.ABC1.D1 = -5678;my_regs.ABC1.E1 = 0x9ABC;my_regs.ABC1.F1 = -3456;my_regs.ABC1.G1 = -7890;my_regs.ABC2.A2 = 0xDEF0;my_regs.ABC2.B2 = 0x1357;my_regs.ABC2.C2 = -9012;my_regs.ABC2.D2 = -3456;my_regs.ABC2.E2 = 0x789A;my_regs.ABC2.F2 = -5678;my_regs.ABC2.G2 = -9012;// 打印结构体的基地址printf("Base address of my_regs: %p\n", (void*)&my_regs);// 打印特定偏移量的数据print_value_at_virtual_address(&my_regs, 0x03e8,0x03e8); // 打印 A1 的值print_value_at_virtual_address(&my_regs, 0x03e9,0x03e8); // 打印 B1 的值print_value_at_virtual_address(&my_regs, 0x03ea,0x03e8); // 打印 C1 的值print_value_at_virtual_address(&my_regs, 0x03eb,0x03e8); // 打印 D1 的值print_value_at_virtual_address(&my_regs, 0x03ec,0x03e8); // 打印 E1 的值print_value_at_virtual_address(&my_regs, 0x03ed,0x03e8); // 打印 F1 的值print_value_at_virtual_address(&my_regs, 0x03ee,0x03e8); // 打印 G1 的值print_value_at_virtual_address(&my_regs, 0x177B,0x177B-0x7); // 打印 A2 的值 //-0x7}
TIP:
最后注意一下,结构体对齐后需要调用#pragma pack(pop) //:恢复之前保存的对齐设置。
不然可能会影响其余定义的结构体
跳跃取址:
之前脑抽了,没想到可以跳跃取址,还在自己算地址偏移量,像这样就不需要自己算取地址偏移量了:
然后顺手修改了传入参数类型与函数需求不符的警告:
#include <stdio.h>
#include <stdint.h> // 用于标准整数类型定义#pragma pack(push, 1) //:将结构体的对齐方式设置为 1 字节,并将当前对齐设置保存到堆栈中。
//#pragma pack(pop):恢复之前保存的对齐设置。
//使用 #pragma pack 可以确保结构体成员之间没有填充字节,从而精确控制内存布局。typedef unsigned short int uint16_t;
typedef signed short int int16_t;//0x03e8 - 0x03ee
typedef struct ABC_regs1
{uint16_t A1;uint16_t B1;int16_t C1;int16_t D1;uint16_t E1;int16_t F1;int16_t G1;
}ABC_regs_1;//0x177B - 0x1781
typedef struct ABC_regs2
{uint16_t A2;uint16_t B2;int16_t C2;int16_t D2;uint16_t E2;int16_t F2;int16_t G2;
}ABC_regs_2;typedef struct Letter_regs
{ABC_regs_1 ABC1;//0x03e8 - 0x03f1 //7成员 14字节 //偏移量 0x00- 0x0cABC_regs_2 ABC2;//0x177B - 0x1781 //7成员 14字节
}letter_regs;#pragma pack(pop) //:恢复之前保存的对齐设置。void print_value_at_virtual_address(const unsigned char * base_address, size_t virtual_address, size_t virtual_address_start)
{const unsigned char* ptr = (const unsigned char*)base_address;size_t offset = (virtual_address - virtual_address_start)*2; // 计算偏移量if (offset < sizeof(letter_regs)) {// 判断数据类型并打印if (offset % sizeof(uint16_t) == 0) {uint16_t value = *(const uint16_t*)(ptr + offset);printf("Value at virtual address 0x%04zx: 0x%04x\n", virtual_address, value);} else if (offset % sizeof(int16_t) == 0) {int16_t value = *(const int16_t*)(ptr + offset);printf("Value at virtual address 0x%04zx: %d\n", virtual_address, value);} else {unsigned char value = ptr[offset];printf("Value at virtual address 0x%04zx: 0x%02x\n", virtual_address, value);}} else {printf("Virtual address 0x%04zx is out of bounds.\n", virtual_address);}
}int main(void)
{letter_regs my_regs;// 填充结构体my_regs.ABC1.A1 = 0x1234;my_regs.ABC1.B1 = 0x5678;my_regs.ABC1.C1 = -1234;my_regs.ABC1.D1 = -5678;my_regs.ABC1.E1 = 0x9ABC;my_regs.ABC1.F1 = -3456;my_regs.ABC1.G1 = -7890;my_regs.ABC2.A2 = 0xDEF0;my_regs.ABC2.B2 = 0x1357;my_regs.ABC2.C2 = -9012;my_regs.ABC2.D2 = -3456;my_regs.ABC2.E2 = 0x789A;my_regs.ABC2.F2 = -5678;my_regs.ABC2.G2 = -9012;// 打印结构体的基地址printf("Base address of my_regs: %p\n", (void*)&my_regs);// 打印特定偏移量的数据print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03e8,0x03e8); // 打印 A1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03e9,0x03e8); // 打印 B1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03ea,0x03e8); // 打印 C1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03eb,0x03e8); // 打印 D1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03ec,0x03e8); // 打印 E1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03ed,0x03e8); // 打印 F1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC1, 0x03ee,0x03e8); // 打印 G1 的值print_value_at_virtual_address((unsigned char*)&my_regs.ABC2, 0x177B,0x177B); // 打印 A2 的值 //-0x7}