当前位置: 首页 > news >正文

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}

http://www.xdnf.cn/news/874297.html

相关文章:

  • Java DLL依赖缺失解决思路和修复过程(Windows版本)
  • JVM 内存结构 详解
  • 【Java】CopyOnWriteArrayList
  • 使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
  • 陈伟霆电视剧《九门》开机 续写传奇热血新篇
  • 【博客X】缤果串口蓝牙网络USB调试助手(总汇)
  • python打卡day44
  • 如何通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式(并进行了训练、推理)
  • SQL 中 IN 和 EXISTS 的区别
  • 局部变量-线程安全
  • 池化层-机器学习
  • 5.Promise,async,await概念(1)
  • 【SpringCloud】Nacos配置中心
  • 【HarmonyOS 5】游戏开发教程
  • 面向文档编程:MoonBit 的创新开发体验
  • 照片按时间自动重命名工具
  • Java异常信息
  • PaddleOCR(1):PaddleOCR介绍
  • 雷达流速仪相关介绍
  • 微信小程序开发一个自定义组件的详细教程
  • Haystack:AI与IoT领域的全能开源框架
  • 996引擎-前端组件:富文本(RichText)
  • 研究探析 | 高速摄像机在一种新型冲击压痕技术及其动态标定方法中的应用
  • unix/linux,sudo,其发展历程详细时间线、由来、历史背景
  • Origin如何仅删除奇数行或偶数行的数据
  • shell脚本总结14:awk命令的使用方法
  • 【力扣链表篇】203.移除链表元素
  • DIC技术助力金属管材全场应变测量:高效解决方案
  • 线程的生命周期与数量设置
  • 鸿蒙Navigation路由导航-基本使用介绍