通过指针偏移访问结构变量值
Posted
技术标签:
【中文标题】通过指针偏移访问结构变量值【英文标题】:Access struct variable value by pointer offset 【发布时间】:2018-10-19 08:42:24 【问题描述】:我有一个看起来像这样的结构:
#pragma pack(1)
typedef struct WHEATHER_STRUCT
uint8_t packetID; // Value 9
uint16_t packetSize; // Value 7
float cloudLayerAltitude; // Value 25000
Wheather_Struct
此结构已正确初始化。由于算法的设计,我需要通过指针偏移来读取这三个属性值。我感谢声明一个数组,该数组具有这些属性的字节大小。就像:
int sizeOfStructAttributes = 1, 2, 4;
最后要访问这些值,请执行以下操作:
pointer = (*this->wheather_struct->packetID)
for (i=0; i<sizeof(sizeOfStructAttributes); i++)
cout << &pointer << ' ';
pointer = pointer + sizeOfStructAttributes[i];
预期结果:
9 7 25000
你能帮帮我吗?
【问题讨论】:
“由于算法的设计,我需要通过指针偏移量读取这三个属性值。”改变算法的设计。处理结构内部通常是个坏主意,而且它不是可移植的解决方案。 我需要这样做,因为我将开发一种算法,该算法使用属性的大小值读取不同结构的值。 您可以使用offsetof。不过,这看起来像 XY problem。 @Yksisarvinen 是对的 - 避免通过偏移量访问结构的内部。它很脆弱并且容易出错。还有更好的方法。 “预期结果:9 7 25000”,因此您还需要变量的大小(可能还需要输入)。 【参考方案1】:您的代码有很多问题,我将尝试全部解决:
1- 您的结构具有填充值,这取决于您所针对的体系结构,可能在第一个成员 (packetID) 之后 3 或 7 个字节,这取决于体系结构和编译器。
2- 你以错误的方式初始化指针,应该是:
pointer = &(this->wheather_struct->packetID);
3- cout 应该是:
cout << *((datatype*)pointer) << ' ';
//datatype should be different in each loop iteration of course.
4- 如果您正在创建此 strcutrue 的数组,我不确定您是否会遇到填充问题。在极少数情况下,当您使用不同的打包和填充时,由于您的代码与使用不同编译器指令编译的其他库混合,甚至在编译期间使用#pragma 修改编译器的行为。
最后我确信根本不需要用指针枚举结构成员。
我鼓励您阅读有关 struct padding 和 packing 的内容,好的起点是关于 SO 的这个问题: Structure padding and packing
【讨论】:
(datatype)((datatype*)pointer)
是错误的。示例代码只是打印出 pointer 的值。大概真正的代码会有一个 switch 语句将指针转换为正确的类型,然后间接通过它。
第一个元素之后有 very 不太可能是 7 个字节。 uint16_t
只需要在两个字节的边界上,而不是在 8 字节的边界上。
@MartinBonner 我可以强制填充有 7 个字节,您需要查看其中的#pragma,我已经更正了转换。
@MartinBonner 请提供另一个在打印输出期间不需要投射指针的示例。【参考方案2】:
可以肯定的是,您将无法手动编写这些偏移量。这绝对不是一种稳定的处理方式,因为您的编译器可能会进行优化,例如aligning your struct members。
你可以这样做:
Wheather_Struct w;
long offsetsOfStructAttributes[3] = 0,
(char*)&w.packetSize - (char*)&w.packetID,
(char*)&w.cloudLayerAltitude - (char*)&w.packetID;
注意这是大小的字节差异。
已经告诉你如何做到这一点,我不得不说就像人们在 cmets 中所说的那样,请找到另一种方法来做到这一点。这是不安全的,除非你完全知道自己在做什么。
【讨论】:
为什么不使用offsetof
宏?
@MartinBonner 完全没有理由。只是这里用于初学者的直接方式。
我使用了编译指示包,所以我将使用偏移量,因为编译器无法更改此偏移量大小值。我错了吗?
无论如何,我不相信这是明确定义的,所以即使你的数学是正确的,即使你严格控制了你的类型的布局,你仍然可能得到不可靠的结果/崩溃/自发的伽马射线暴。请记住,编译器非常复杂,并且在将您的代码翻译成真正的计算机程序时依赖于标准强加的约束,这涉及基于别名规则和各种东西的“优化”。一些不错的指向成员的指针怎么样?
@TheQuantumPhysicist 我并不是说你的答案是错误的;我的评论是对每个人的补充。但是,我必须解决您评论中的误解,因为它没有抓住要点 - 编译器不会愚蠢到在这里提供错误的地址,它们太聪明无法保证这个无效代码的预期结果。那是 如果 我对 UB 的看法是正确的;没有把握。不要错误地认为 C++ 程序在某种程度上是对那些 CPU 指令和内部结构表示的一对一映射。不是。【参考方案3】:
您的错误是您假设该类在成员之间没有填充。但是必须有填充才能满足成员的对齐要求。因此,偏移量不是您所假设的。
要获取类成员的偏移量,您可以使用标准库提供的offsetof
宏。也就是说,在不知道您需要它的情况下,我仍然怀疑它是否合适。请注意,offsetof
仅在您的类是标准布局类时才有效。否则行为将是未定义的。您的示例 WHEATHER_STRUCT
是标准布局。
cout << &pointer << ' ';
这样的东西不可能有你期望的输出。您获取指针的地址,它不可能给您想要的指向对象的值。
获取指向值的方法是间接运算符。但是,间接运算符只有在指针类型正确时才能正常工作(float*
用于浮点成员,uint16_t*
用于 uint16_t 成员......)但它不能是正确类型,因为它必须是指向字节的指针让指针算法与偏移量一起工作。
除了偏移量之外,您还需要知道变量的类型才能解释值。您可以将类型存储在某种结构中。但是您不能将指针转换为在运行时确定的类型,因此您需要一些运行时流结构,例如 switch
或用于转换的跳转表。
【讨论】:
实际上,OP 假设第一个对象在偏移量 0 处。【参考方案4】:您最好不要使用指针破解:有一天底层内存布局将被更改,您的程序可能会损坏它。 尝试模拟元数据。
enum WheatherStructFields
wsfPacketID,
wsfPacketSize,
wsfCloudLayerAltitude,
wsfNone
;
typedef struct WHEATHER_STRUCT
uint8_t packetID;
uint16_t packetSize;
float cloudLayerAltitude;
void OutFieldValue(std::ostream& os, WheatherStructFields whatField)
switch (whatField)
case wsfPacketID:
os << (int)packetID;
break;
case wsfPacketSize:
os << packetSize;
break;
case wsfCloudLayerAltitude:
os << cloudLayerAltitude;
break;
default:
os << "Unsupported field: " << whatField;
Wheather_Struct;
int main()
Wheather_Struct weather = 9, 7, 25000 ;
for (WheatherStructFields whatField = wsfPacketID; whatField < wsfNone;
whatField = (WheatherStructFields)((int)whatField + 1))
weather.OutFieldValue(std::cout, whatField);
std::cout << " ";
【讨论】:
我使用了编译指示包,所以我将使用偏移量,因为编译器无法更改此偏移量大小值。我错了吗? 尝试不同的编译器、平台和目标,以确保您的 hack 仍然有效。 也尝试添加新字段。猜猜某人(人类)或自动代码格式化程序可以重新排序它。为什么需要产生气味和脆弱的代码?【参考方案5】:你的方法有两个问题:
首先,它要求您选择正确的尺寸。使用sizeof
来做到这一点。所以你的数组看起来像:
size_t sizeOfStructAttributes = sizeof(wheather_struct::packet_id),
sizeof(wheather_struct::packet_size),
sizeof(wheather_struct::cloudLayerAltitude) ;
第二个(更严重的)问题是您不允许在结构中进行填充。几乎所有编译器都会(除非特别指示)在 packet_id 和 packet_size 之间插入一个填充字节,以便一切都很好地对齐。幸运的是,也有解决方案 - 使用 offsetof
宏(在 stddef.h 中定义):
size_t offsetOfStructAttributes = offsetof(wheather_struct, packet_id),
offsetof(wheather_struct, packet_size),
offsetof(wheather_struct, cloudLayerAltitude) ;
然后代码变成:
for (size_t offset: offsetsOfStructAttributes)
pointer = &(this->wheather_struct->packetID) + offset
cout << pointer << ' ';
实际上:上面的代码解决了您的代码的第三个问题:sizeof()
返回以字节为单位的大小,这不太可能是元素计数。
最后,您的变量有一个错字:气象学关注的是天气是否会好。您混淆了这两个词,我很确定您的意思是“天气”。
【讨论】:
Jarod42:感谢您修复该问题 - 两次 :-)。我真的不是想回滚你的更改! “几乎所有编译器都会(除非特别指示)”我为此声明了一次 pragma,因为我将使用此结构的值创建一个 UDP 套接字。 "Pragma once" 只是添加标头包含防护的非标准方式。你是说pragma pack吗?如果是这样,您应该在回答中提及这一点。 是的,对不起,我提到了编译指示包。我将编辑帖子。 你确定问这个问题的人是想打印指针的地址吗?这是错误的以上是关于通过指针偏移访问结构变量值的主要内容,如果未能解决你的问题,请参考以下文章
访问 php 中的 JavaScript 变量值以存储在 mysql 中
C 语言一级指针 易犯错误 模型 ( 判定指针合法性 | 数组越界 | 不断修改指针变量值 | 函数中将栈内存数组返回 | 函数间接赋值形参操作 | 指针取值与自增操作 )