首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C语言 >

C里怎么知道连个结构变量之间有多少个字节

2012-02-20 
C里如何知道连个结构变量之间有多少个字节?比如:typedefstruct{intaintbintcchard[10]chareintf[20]

C里如何知道连个结构变量之间有多少个字节?
比如:  
 
  typedef   struct
  {
        int   a;
        int   b;
        int   c;
        char   d[10];
        char   e;
        int   f[20];
        int   g;
  }
  如果想知道结构变量   b   之后与结构变量   g   之前,中间占多少个字节,应该怎么处理?
  当然一个个算也是可以,但是要是以后增加其他的数据,又要重新算,不好维护!
  请高手支招   !谢谢!
 


[解决办法]
结构体变量一般的编译器会内存对齐,这样得不精确的结论
[解决办法]
但是要是以后增加其他的数据,又要重新算,不好维护!
--------
结构体定义好了难道以后还会不停的改?
[解决办法]
取地址相减 ...
[解决办法]
typedef struct
{
int a;
int b;
int c;
char d[10];
char e;
int f[20];
int g;
} A;

&((A*)0)-> g - &((A*)0)-> b;
[解决办法]
取地址相减 ...
这方法挺好的
但如果使用了地址对齐,估计还是有问题吧
[解决办法]
typedef struct
{
int a;
int b;
int c;
char d[10];
char e;
int f[20];
int g;
}s;

int main( void )
{
s s1;
cout < <(&s1.g-&s1.b) < <endl;
return 0 ;
}
[解决办法]
#define offset(type,f) ((size_t) \
((char *)&((type *)0)-> f - (char *)(type *)0))

分别计算g和b的偏移,
然后 计算差值即可 ....
[解决办法]
#include <stdio.h>
#include <stdlib.h>

#define offset(type,f) ((size_t) \
((char *)&((type *)0)-> f - (char *)(type *)0))

typedef struct
{
int a;
int b;
int c;
char d[10];
char e;
int f[20];
int g;
}test;

int main(int argc, char *argv[])
{
printf( "%d\n ", offset(test, g)-offset(test, b));
system( "pause ");
return 0;
}
[解决办法]
忽略内存对齐,暴力的方法,个人觉得可以
struct Type
{
int a;
int b;
char c;
}s;

int main()
{
char *p1 = (char *)&(s.a);
char *p2 = (char *)&(s.b);
char *p3 = (char *)&(s.c);

cout < < p2 - p1 < < endl;
cout < < p3 - p2 < < endl;
cout < < p3 - p1 < < endl;

return 0;
}
[解决办法]
仰视一下4颗星星
[解决办法]
学习

[解决办法]
或者手动计算分析也一样:
typedef struct
{
int a;
int b; //从这里开始, 1 int,4字节, 位于对齐边界, 无填充
int c; //1 int,4字节, 位于对齐边界, 无填充
char d[10]; //10字节, 位于对齐边界, 无填充
char e; //1字节, 位于对齐边界, 无填充
int f[20]; //4*20字节, 起始元素需要对齐,填充1字节


int g; //到这里结束
}

共:4+4+10+1+(1)+80=100 字节

自己根据对齐情况分析一下即可。

PS:上述分析在 pack 默认或者指定 pack 大于 int align 的情况下适用。
[解决办法]
结构体系统会优化做对齐动作, 偶觉得如果你在b之前 g之后都可能添加新的成员的话 偶认为你无法得到精确的 答案。

偶的方法就是 整个结构体占的空间 减去 a b 和 g 就是中间的。前提是 b之前不加东西
[解决办法]
增加成员在后面追加就是了。
[解决办法]
看看 C99 标准中 灵活数组成员:

结构体变长的妙用——0个元素的数组
有时我们需要产生一个结构体,实现了一种可变长度的结构。如何来实现呢?
看这个结构体的定义:
typedef struct st_type
{
int nCnt;
int item[0];
}type_a;
(有些编译器会报错无法编译可以改成:)
typedef struct st_type
{
int nCnt;
int item[];
}type_a;
这样我们就可以定义一个可变长的结构,用sizeof(type_a)得到的只有4,就是sizeof(nCnt)=sizeof(int)那

个0个元素的数组没有占用空间,而后我们可以进行变长操作了。
C语言版:
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
C++语言版:
type_a *p = (type_a*)new char[sizeof(type_a)+100*sizeof(int)];
这样我们就产生了一个长为100的type_a类型的东西用p-> item[n]就能简单地访问可变长元素,原理十分简单

,分配了比sizeof(type_a)多的内存后int item[];就有了其意义了,它指向的是int nCnt;后面的内容,是没

有内存需要的,而在分配时多分配的内存就可以由其来操控,是个十分好用的技巧。
而释放同样简单:
C语言版:
free(p);
C++语言版:
delete []p;
其实这个叫灵活数组成员(fleible array member)C89不支持这种东西,C99把它作为一种特例加入了标准。但

是,C99所支持的是incomplete type,而不是zero array,形同int item[0];这种形式是非法的,C99支持的

形式是形同int item[];只不过有些编译器把int item[0];作为非标准扩展来支持,而且在C99发布之前已经有

了这种非标准扩展了,C99发布之后,有些编译器把两者合而为一。
下面是C99中的相关内容:
6.7.2.1 Structure and union specifiers

As a special case, the last element of a structure with more than one named member may have

an incomplete array type; this is called a flexible array member. With two exceptions, the

flexible array member is ignored. First, the size of the structure shall be equal to the offset

of the last element of an otherwise identical structure that replaces the flexible array member

with an array of unspecified length.106) Second, when a . (or -> ) operator has a left operand

that is (a pointer to) a structure with a flexible array member and the right operand names that

member, it behaves as if that member were replaced with the longest array (with the same element

type) that would not make the structure larger than the object being accessed; the offset of the

array shall remain that of the flexible array member, even if this would differ from that of the

replacement array. If this array would have no elements, it behaves as if it had one element but

the behavior is undefined if any attempt is made to access that element or to generate a pointer

one past it.
例如在VC++6里使用两者之一都能通过编译并且完成操作,而会产生warning C4200: nonstandard extension

used : zero-sized array in struct/union的警告消息。
而在DEVCPP里两者同样可以使用,并且不会有警告消息。
[解决办法]
使用结构体末尾的 灵活数组 成员可以比较灵活的 追加元素,
而且在不追加的时候不浪费内存空间。
[解决办法]

好帖
[解决办法]
不错,我都没试过结构体还可以这么用啊
[解决办法]
用offsetof最 方便了
[解决办法]
((size_t)(&((type *)0)-> g)) - ((size_t)(&((type *)0)-> b))
------解决方案--------------------


mark
[解决办法]
受教
[解决办法]
在嵌入式应用中,或许你对offsetof接触不多甚至根本没见过。如果是这样,那么从这一刻起就好好地掌握它,让它成为你的又一杀手锏吧。
1. offsetof与EEPROM
我们许多人可能都使用过一些非挥发性的存储器,如常见的EEPROM。我们经常使用它们在存储一些系统的配置参数和设备信息。在所有的EEPROM中,通过串口访问的占了大多数。一般来说,对串口的访问都是按字节进行的,这使得我们不可避免会设计出下面的
接口去访问EEPROM的信息:
/*从EEPROM 偏移量offset处读取nBytes到RAM地址dest*/
ee_rd(uint16_t offset, uint16_t nBytes, uint8_t * dest);
然而,这种接口必须要知道偏移量offset和读取字节数nBytes。可能你会采用下面的方法解决方法解决这个问题:
定义一个数据结构和一个指向这个数据结构的指针,并初始化这个指针为EEPROM的起始地址EEPROM_BASE.
---------------------------- <-EPPROM_BASE:0x0000000
| i | f | c | | | |...
----------------------------
| | | | | | |...
----------------------------
| | | | | | |...
----------------------------
...
----------------------------
#define EEPROM_BASE 0x0000000/*配置信息的起始地址*/
typedef struct
{
int i;
float f;
char c;
} EEPROM;
EEPROM * const pEE = EEPROM_BASE
ee_rd(&(pEE-> f), sizeof(pEE-> f), dest);
没错,这种方法的确可以达到访问指定地址的信息。不过这种方法也存在下面的问题:
a.容易使代码维护人员人误以为在ee_rd接口内部也存在EEPROM的数据结构。
b.当你编写一些自己感觉良好编译器不报错的代码,比如pEE-> f = 3.2,你可能意想不到灾难将要来临。
c.这个接口没有很好地体现EEPROM所隐含的硬件特性。
到这里,有人可能会想到offsetof(那些没用过甚至没见过的朋友别急,后面马上会详解offsetof)来解决这个问题:
/*offsetof获取数据成员在数据结构中的偏移量
比如成员f在EEPROM数据结构中的偏移量,这里为什么
要强制转化0,这是个有深度的问题,在后面也会详细说明*/
#define offsetof(type, f) ((size_t) \
((char *)&((type *)0)-> f - (char *)(type *)0))
typedef struct
{
int i;
float f;
char c;
} EEPROM;
ee_rd(offsetof(EEPROM,f), 4, dest);
如果你能想到这里说明你对offsetof有一定程度的理解,不过还可以改进。如果让编译器来计算nBytes而不是我们自己给出4那就更好了。这时,一定有人会马上提到sizeof。是的。可是怎么使用呢,我们不能用sizeof(EEPROM.f)来计算nBytes吧?!我想那些对offsetof有较深理解的同志一定会这么办:
/*类似于offsetof的定义*/
#define SIZEOF(s,m) ((size_t) sizeof(((s *)0)-> m))
ee_rd(offsetof(EEPROM, f), SIZEOF(EEPROM, f), &dest);
很不错! 其实还可以精简为下面的最终形式:
#define EE_RD(M,D) ee_rd(offsetof(EEPROM,M), SIZEOF(EEPROM,M), D)
EE_RD(f, &dest);
哈哈,这样我们只用传递两个参数,不用再考虑应该从那里读取数据以及读取多少的问题。
先打住,有人会说这种简化都是建立在EEPROM_BASE为0x0000000基础之上的,可能会反问,如果配置信息不是从0地址开始的呢?

Good question.其实我们可以通过下面的方法解决。
#define EEPROM_BASE 0x00000a10
typedef struct
{
char pad[EEPROM_BASE];/*使数据结构的前EEPROM_BASE个字节填 "空 "*/
int i;
float f;
char c;
} EEPROM;

---------------------------- 0x00000000
| | | | | | |...
----------------------------
...
---------------------------- <-EPPROM_BASE:0x00000a10
| i | f | c | | | |...
----------------------------
| | | | | | |...
----------------------------
...

[解决办法]
使用offsetof简化EEPROM的串口访问的确很妙。这里还有一个很好的例子。
在嵌入式应用中,我们时常将一些I/O寄存器映射到内存地址空间进行访问。
这种映射使原本复杂的寄存器访问变得象访问普通的RAM地址一样方便。
在我们视频会议系统中,PowerPC 8250访问外部的ROM控制器(ROM controller)的
寄存器就是通过这种方式实现的。ROM控制器所有的寄存器被映射到从I/O寄存器空间基地址0x10000000(IO_BASE)偏移0x60000(ROMCONOffset)字节的一段内存。每个寄存器占用四个字节,并有一个数据结构与它们对应。比如控制ROM控制器工作状态的寄存器对应数据结构ROMCON_ROM_CONTROL,配置PCI总线A的寄存器对应数据结构ROMCON_CONFIG_A,下面先看看这些数据结构的定义:
#define IO_BASE 0x10000000
#define ROMCONOffset 0x60000
typedef unsigned int NW_UINT32;
typedef struct _ROMCON_CONFIG_A {
union {
struct {
UINT32 pad4:21; /* unused */
UINT32 pad3:2; /* reserved */
UINT32 pad2:5; /* unused */
UINT32 EnablePCIA:1;
UINT32 pad1:1; /* reserved */
UINT32 EnableBoot:1;
UINT32 EnableCpu:1; /*bit to enable cpu*/
} nlstruct;
struct {
UINT32 ConfigA;
} nlstruct4;
} nlunion;
} ROMCON_CONFIG_A, *PROMCON_CONFIG_A;


typedef struct _ROMCON_ROM_CONTROL {
union {
struct {
UINT32 TransferComplete:1;
UINT32 pad3:1; /* unused */
UINT32 BondPad3To2:2;
UINT32 Advance:3;
UINT32 VersaPortDisable:1;
UINT32 pad2:1; /* unused */
UINT32 FastClks:1;
UINT32 pad1:7; /* unused */
UINT32 CsToFinClks:2;
UINT32 OeToCsClks:2;
UINT32 DataToOeClks:2;
UINT32 OeToDataClks:3;
UINT32 CsToOeClks:2;
UINT32 AddrToCsClks:2;
UINT32 AleWidth:2;
} nlstruct;
struct {
UINT32 RomControl;
} nlstruct4;
} nlunion;
} ROMCON_ROM_CONTROL, *PROMCON_ROM_CONTROL;
typedef struct
{
ROMCON_CONFIG_A ConfigA;
ROMCON_CONFIG_B ConfigB;
ROMCON_ROM_CONTROL RomControl;
...
}ROMCON, *PROMCON;
---------------------------- <-IO_BASE:0x10000000
| | | | | | |...
----------------------------
| | | | | | |...
...
---------------------------- <-ROMCONOffset(ROMCON):0x60000
| | | | | | |...
---------------------------- <-ROMCON_ROM_CONTROL
...
----------------------------
那么如何访问ROMCON_ROM_CONTROL对应寄存器呢,比如ROMCON_ROM_CONTROL对应寄存器的VersaPortDisable位?
估计有人可能会这样做:
事先定义成员RomControl(ROMCON中用ROMCON_ROM_CONTROL定义的实例)相对与ROMCON的偏移量,
#define ROMCONRomControlOffset 0x8
然后设计访问ROM的接口如下:
/*读取ROM控制器位于src位置的寄存器数据到dest*/
typedef unsigned long dword_t;

void rom_read(dword_t* src, uint32_t* dest);
void rom_write(dword_t* src, uint32_t* dest);
最后利用这个偏移量做下面的操作:
ROMCON_ROM_CONTROL tRomCtrl={0};
dword_t* pReg=(dword_t*)(IO_BASE+ROMCONOffset+\
ROMCONRomControlOffset);
rom_read(pReg,(uint32_t)*(&tRomCtrl));
/*查看寄存器的VersaPortDisable位,如果该位没有启用就启用它*/
if(!tRomCtrl.nlunion.nlstruct.VersaPortDisable)
{
tRomCtrl.nlunion.nlstruct.VersaPortDisable = 1;
rom_write(pReg,(uint32_t)*(&tRomCtrl));
}

这样做确实可以达到访问相应寄存器的目的。但是,如果和ROM相关的寄存器很多,那么定义、记忆和管理那么多偏移量不是很不方便吗?到这里,如果你对前面关于offsetof还有印象的话,我想你可能会作下面的优化:
#define ROMCON_ADDR(m) (((size_t)IO_BASE+\
(size_t)ROMCONOffset+\
(size_t)offsetof(ROMCON,m))
ROMCON_ROM_CONTROL tRomCtrl={0};
dword_t* pReg=(dword_t*)ROMCON_ADDR(ConfigA);
rom_read(pReg,(uint32_t)*(&tRomCtrl));
/*查看寄存器的VersaPortDisable位,如果没有启动就启动它*/
if(!tRomCtrl.nlunion.nlstruct.VersaPortDisable)
{
tRomCtrl.nlunion.nlstruct.VersaPortDisable = 1;
rom_write(pReg,(uint32_t)*(&tRomCtrl));
}

2.offsetof的来龙去脉
通过前面的举例,你可能对如何使用offsetof已经不陌生了吧。offsetof对那些搞
C++的人可能很熟悉,因为offsetof类似于sizeof,也是一种系统操作符,你不用考虑它是怎么定义的。这个操作符offsetof的定义可以在ANSI C 编译器所带的stddef.h中找到。在嵌入式系统里,不同开发商,不同架构处理器和编译器都有不同的offsetof定义形式:
/* Keil 8051 */
#define offsetof(s,m) (size_t)&(((s *)0)-> m)
/* Microsoft x86 */
#define offsetof(s,m) (size_t)(unsigned long)&(((s *)0)-> m)
/* Motorola coldfire */
#define offsetof(s,memb) ((size_t)((char *)&((s *)0)-> memb-(char *)0))
/* GNU GCC 4.0.2 */
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
虽然定义形式不同,但功能都是返回成员在数据结构中的偏移量,都是为了提高代码的可移植性。
下面拿KEIL 8051的定义来作点解释:
((s *)0):强制转化成数据结构指针,并使其指向地址0;
((s *)0)-> m:使该指针指向成员m
&(((s *)0)-> m):获取该成员m的地址
(size_t)&(((s *)0)-> m):转化这个地址为合适的类型
你可能会迷惑,这样强制转换后的结构指针怎么可以用来访问结构体字段?呵呵,其实这个表达式根本没有也不打算访问m字段。ANSI C标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此((s*)0)的结果就是一个类型为s*的NULL指针。如果利用这个NULL指针来访问s的成员当然是非法的,但&(((s*)0)-> m)的意图并非想存取s字段内容,而仅仅是计算当结构体实例的首址为((s*)0)时m字段的地址。聪明的编译器根本就不生成访问m的代码,而仅仅是根据s的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。又因为首址的值为0,所以这个地址的值就是字段相对于结构体基址的偏移。
这里有个地方需要注意:就是offsetof虽然同样适用于union结构,但它不能用于计算位域(bitfield)成员在数据结构中的偏移量。
typedef struct
{
unsigned int a:3;
unsigned int b:13;
unsigned int c:16;
}foo;
使用offset(foo,a)计算a在foo中的偏移量,编译器会报错;


[解决办法]
mark
[解决办法]
支持瞌睡虫的做法,拜唐朝,NB,又学到不少……
[解决办法]
Good~学习中...
[解决办法]

灵活数组是不错,但也不是无限长度地。这个试一下就知道了。

我们编成的时候,为了对付内存对齐,一般占字节多的类型放在结构体前面定义。
[解决办法]
mark
[解决办法]
对于offsetof 来说的话,
比如说 按照 4字节对齐:
typedef struct A
{
char c;//4
short s;//4
int i; //4
long l;//4
}A;
那么编译offsetof(A,s)的时候, 应该就会按照4来取值的吧.
同样offsetof(A,l),就会按照12来取值.
因此s和l之间的距离也就是8了.

但是这需要在确定对齐字节数的情况下,此值才会不变.否则是易变的.

[解决办法]
typedef struct
{
int a;//4
int b;//4
int c;//4
char d[10];//10
char e;//1,补1
int f[20];//20*4
int g;//4
};
所以应该是96字节吧?

热点排行