1

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

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”字段可能是上述结构中的任何一个,我现在将修改它们的类型为

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 语言中的“安全”?

4

4 回答 4

1

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

s *a = p;

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

或者

((s *) p)->i1 = 42;
((s *) p)->i2 = 31; 
于 2012-11-19T13:07:30.637 回答
1

你可以投它:

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

我没有看到任何entity_flag结构s,但如果你expr的意思同样适用:

unsigned char flag=((expr*)vp)->entity_flag;
于 2012-11-19T13:07:59.120 回答
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;

任何其他解决方案都是令人讨厌的黑客,IMO,并且大多数转换解决方案都将冒着违反“严格别名”规则的风险,即允许编译器假设两个不同类型的指针不能引用相同的内存。


编辑 ...

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

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

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

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

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

于 2012-11-19T13:14:04.097 回答
1

如果您知道 struct 成员所在位置的偏移量,则可以进行指针运算,然后根据 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

使用联合不一定会为数字使用更多内存。基本上一个数字只需要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;

} 表达式;

于 2012-11-19T13:55:36.323 回答