【问题标题】:How to access members through a void pointer如何通过 void 指针访问成员
【发布时间】:2012-11-19 13:04:41
【问题描述】:

从尝试编写一个将基本算术翻译成英文的小程序开始,我最终构建了一个二叉树(它不可避免地非常不平衡)来表示评估的顺序。 首先,我写了

struct expr;

    typedef struct{
    unsigned char entity_flag;  /*positive when the oprd
    struct represents an entity 
     ---a single digit or a parenthesized block*/                      
    char oprt;

    expr * l_oprd;// these two point to the children nodes 
    expr * r_oprd;
    } expr;

但是,为了有效地表示单个数字,我更喜欢

typedef struct{
 unsigned char entity_flag;
 int ival;
} digit;

由于现在每个“expr”结构的“oprd”字段可能是上述任何一个struct-s,我现在将它们的类型修改为

void * l_oprd;
void * r_oprd;

然后是“中心问题”: 如何通过 void 指针访问成员? 请看以下代码

#include<stdio.h>
#include<stdlib.h>


typedef struct {
int i1;
int i2;} s;
main(){
void* p=malloc(sizeof(s));

//p->i1=1;
//p->i2=2;

*(int*)p=1;
*((int *)p+1)=2;
printf("s{i1:%d, i2: %d}\n",*(int*)p,*((int *)p+1));
}

编译器不接受注释版本! 我必须用上面杂乱的方法来做吗?

请帮忙。

PS:如你所见,上面的每个 struct-s 都有一个名为“entity_flag”的字段,因此

void * vp;
...(giving some value to vp)
unsigned char flag=vp->entity_flag;

无论 void 指向什么,都可以提取标志,这在 C 中是否允许?甚至是 C 语言中的“安全”?

【问题讨论】:

  • struct s 没有任何entity_flag
  • @mux: struct "s" 是用来试验指向单独 .c 文件中结构的 void 指针;不是 entity_flag 相关的项目的一部分,希望这不是投反对票的原因
  • 我没有投反对票,但您的问题并不具体。

标签: c syntax void-pointers


【解决方案1】:

您不能通过void * 指针访问成员。您可以通过多种方式进行转换(实际上,您甚至需要使用void * 明确说明情况),但即使这样也是错误答案

正确答案是使用union

typedef union {
  struct{
    unsigned char entity_flag;  /*positive when the oprd
    struct represents an entity 
     ---a single digit or a parenthesized block*/                      
    char oprt;

    expr * l_oprd;// these two point to the children nodes 
    expr * r_oprd;
  } expr;
  struct{
    unsigned char entity_flag;
    int ival;
  } digit;
} expr;

然后你访问这样的表达式(给定一个变量expr *e):

e->expr->entity_flag;

还有这样的数字:

e->digit->entity_flag;

任何其他解决方案都是令人讨厌的 hack,IMO,并且大多数转换解决方案都有可能违反“严格别名”规则,即允许编译器假定两个不同类型的指针不能引用相同的内存.


编辑 ...

如果您需要能够检查数据本身以找出正在使用的工会成员,您可以。

基本上,如果两个结构中最顶层的字段声明相同,那么它们将具有相同的二进制表示。这不仅限于联合,在为该架构编译的所有二进制文件中通常都是如此(如果您考虑一下,这对于库的工作至关重要)。

在联合中,通常会将它们拉出到一个单独的结构中,以便您在做什么很明显,尽管这不是必需的:

union {
  struct {
    int ID;
  } base;
  struct {
    int ID;
    char *data
  } A;
  struct {
    int ID;
    int *numeric_data;
  } B;
}

在此方案中,p-&gt;base.IDp-&gt;A.IDp-&gt;B.ID 保证读取相同。

【讨论】:

  • 所以sockaddr 是一个讨厌的黑客:) ?
  • 是的,是的,但这并不危险,因为您不会以一种形式编写并以另一种形式阅读,当然也不使用相同的功能。
  • sockaddr 是这样的,因为union 解决方案不允许太多的模块化或可扩展性,这是一种耻辱。
  • 我写了“digit”结构,因为我想用更少的空间来表示单个数字,联合解决方案会破坏这个目的,因为 sizeof(expr) 将在只有 sizeof(digit) 是必要的时候被分配。
  • 只为您计划使用的字段分配足够的空间是非常安全的。不安全的是违反别名规则。
【解决方案2】:

只需将p 转换为相关的指针类型:

s *a = p;

a->i1 = 42;
a->i2 = 31;

((s *) p)->i1 = 42;
((s *) p)->i2 = 31; 

【讨论】:

    【解决方案3】:

    你可以施放它:

    ((s*)p)->i1=1;
    ((s*)p)->i2=2;
    

    我在 struct s 中看不到任何 entity_flag,但如果您的意思是 expr,同样适用:

    unsigned char flag=((expr*)vp)->entity_flag;
    

    【讨论】:

      【解决方案4】:

      如果您知道结构成员所在位置的偏移量,您可以进行指针运算,然后根据 entity_flag 的值转换为适当的类型。

      我强烈建议以字节为单位对齐两个结构,并为 oprt 和 digit 使用相同的字节数。

      此外,如果您的树中只有 oprt 和 digit “类型”,您可以牺牲第一位精度来标记 digit 或 oprt,并节省 unsigned char entity_flag 所需的空间。如果您对 oprt 和 digit 使用单个 4 字节 int var 并使用第一位对类型进行编码,您可以通过(使用联合解决方案模式:在线程中提出)提取数字

      typedef union {
          struct {
              int code;
              expr * l_expr;
              expr * r_expr;
          } oprt;
          struct {
             int val;
          } digit;
      } expr;
      
      expr *x;
      int raw_digit = x->digit.val;
      
      int digit = raw_digit | ((0x4000000 & raw_digit) << 1 ) // preserves sign in 2's complement 
      
      x->digit.val = digit | 0x8000000                       // assuming MSB==1 means digit
      

      使用 union 并不一定会为数字使用更多内存。基本上一个数字只需要4个字节。因此,每次您需要分配数字类型 expr 时,您只需调用 malloc(4),将结果转换为 *expr,并将 MSB 相应地设置为 1。如果您对 expr 指针进行编码和解码而没有错误,那么您将永远不会尝试超出“数字”类型 expr 的第 4 个字节......希望如此。如果您需要安全,我不推荐此解决方案^_^

      要轻松检查 expr 类型,我相信您可以在联合内使用位域:

      typedef union {
         struct {
             int code;
             expr * l_expr;
             expr * r_expr;
         } oprt;
         struct {
             int val;
         } digit;
         struct {
             unsigned int is_digit : 1;
             int : 31; //unused
         } type;
      

      } 表达式;

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-24
        • 2015-12-17
        • 1970-01-01
        • 2020-02-06
        • 1970-01-01
        • 2014-05-14
        • 1970-01-01
        相关资源
        最近更新 更多