In-depth exploration of struct usage

This article has a good analysis of the role of struct, it is worth a look!


1. The huge role of struct

  When faced with a person's large C/C++ program, we can evaluate the programmer's programming experience just by looking at its use of struct. Because a large C/C++ program is bound to involve some (or even a large number) of structures for data combination, these structures can combine data that originally belong to a whole. To a certain extent, whether or not to use struct and how to use struct is a sign to distinguish whether a developer has rich development experience.

  In the C/C++ programming of network protocols, communication control, and embedded systems, what we often want to transmit is not a simple byte stream (char array), but a whole combination of various data, whose manifestation is a structure.

  Inexperienced developers often store all the content that needs to be transmitted in a char type array in sequence, and transmit information such as network packets by means of pointer offset. The programming is complex and error-prone, and once the control method and communication protocol are changed, the program must be modified in great detail.

  An experienced developer uses the structure flexibly. As an example, suppose three types of packets need to be transmitted in the network or control protocol, and their formats are packetA, packetB, and packetC:

struct structA
{
 int a;
 char b;
};

struct structB
{
 char a;
 short b;
};

struct structC
{
 int a;
 char b;
 float c;
}

  Good programmers design the transmitted messages like this:

struct CommuPacket
{
 int iPacketType;  //报文类型标志
 union      //每次传送的是三种报文中的一种,使用union
 {
  struct structA packetA;
  struct structB packetB;
  struct structC packetC;
 }
};

  在进行报文传送时,直接传送struct CommuPacket一个整体。

  假设发送函数的原形如下:

// pSendData:发送字节流的首地址,iLen:要发送的长度
Send(char * pSendData, unsigned int  iLen);
发送方可以直接进行如下调用发送struct CommuPacket的一个实例sendCommuPacket:
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
假设接收函数的原形如下:
// pRecvData:发送字节流的首地址,iLen:要接收的长度
//返回值:实际接收到的字节数
unsigned int Recv(char * pRecvData, unsigned int  iLen);

  接收方可以直接进行如下调用将接收到的数据保存在struct CommuPacket的一个实例recvCommuPacket中:

Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );

  接着判断报文类型进行相应处理:

switch(recvCommuPacket. iPacketType)
{
    case PACKET_A:
    …    //A类报文处理
    break;
    case PACKET_B:
    …   //B类报文处理
    break;
    case PACKET_C:
    …   //C类报文处理
    break;
}

  以上程序中最值得注意的是

Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );

  中的强制类型转换:(char *)&sendCommuPacket、(char *)&recvCommuPacket,先取地址,再转化为char型指针,这样就可以直接利用处理字节流的函数。

  利用这种强制类型转化,我们还可以方便程序的编写,例如要对sendCommuPacket所处内存初始化为0,可以这样调用标准库函数memset():

memset((char *)&sendCommuPacket,0, sizeof(CommuPacket));

2. struct的成员对齐

  Intel、微软等公司曾经出过一道类似的面试题:

1. #include

2. #pragma pack(8)
3. struct example1
4. {
5. short a;
6. long b;
7. };

8. struct example2
9. {
10. char c;
11. example1 struct1;
 12. short e;   
13. };
14. #pragma pack()

15. int main(int argc, char* argv[])
16. {
 17. example2 struct2;

18. cout << sizeof(example1) << endl;
19. cout << sizeof(example2) << endl;
20. cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2)
<< endl;

21. return 0;
22. }

  问程序的输入结果是什么?

  答案是:

8
16
4

  不明白?还是不明白?下面一一道来:

2.1 自然对界

  struct是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如array、struct、union等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。缺省情况下,编译器为结构体的每个成员按其自然对界(natural alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

  自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中size最大的成员对齐。

  例如:

struct naturalalign
{
 char a;
 short b;
 char c;
};

  在上述结构体中,size最大的是short,其长度为2字节,因而结构体中的char成员a、c都以2为单位对齐,sizeof(naturalalign)的结果等于6;

  如果改为:

struct naturalalign
{
 char a;
 int b;
 char c;
};

  其结果显然为12。

2.2指定对界

  一般地,可以通过下面的方法来改变缺省的对界条件:

  · 使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
  · 使用伪指令#pragma pack (),取消自定义字节对齐方式。

  注意:如果#pragma pack (n)中指定的n大于结构体中最大成员的size,则其不起作用,结构体仍然按照size最大的成员进行对界。

  例如:

#pragma pack (n)
struct naturalalign
{
 char a;
 int b;
 char c;
};
#pragma pack ()

  当n为4、8、16时,其对齐方式均一样,sizeof(naturalalign)的结果都等于12。而当n为2时,其发挥了作用,使得sizeof(naturalalign)的结果为8。

  另外,通过__attribute((aligned (n)))也可以让所作用的结构体成员对齐在n字节边界上,但是它较少被使用,因而不作详细讲解。

2.3 面试题的解答

  至此,我们可以对Intel、微软的面试题进行全面的解答。

  程序中第2行#pragma pack (8)虽然指定了对界为8,但是由于struct example1中的成员最大size为4(long变量size为4),故struct example1仍然按4字节对界,struct example1的size为8,即第18行的输出结果;

  struct example2中包含了struct example1,其本身包含的简单数据成员的最大size为2(short变量e),但是因为其包含了struct example1,而struct example1中的最大成员size为4,struct example2也应以4对界,#pragma pack (8)中指定的对界对struct example2也不起作用,故19行的输出结果为16;

  由于struct example2中的成员以4为单位对界,故其char变量c后应补充3个空,其后才是成员struct1的内存空间,20行的输出结果为4。

3. C和C++间struct的深层区别

  在C++语言中struct具有了“类” 的功能,其与关键字class的区别在于struct中成员变量和函数的默认访问权限为public,而class的为private。

  例如,定义struct类和class类:

struct structA
{
char a;

}
class classB
{
      char a;
      …
}

  则:

struct A a;
a.a = 'a';    //访问public成员,合法
classB b;
b.a = 'a';    //访问private成员,不合法

  许多文献写到这里就认为已经给出了C++中struct和class的全部区别,实则不然,另外一点需要注意的是:

  C++中的struct保持了对C中struct的全面兼容(这符合C++的初衷——“a better c”),因而,下面的操作是合法的:

//定义struct
struct structA
{
char a;
char b;
int c;
};
structA a = {'a' , 'a' ,1};    //  定义时直接赋初值

  即struct可以在定义的时候直接以{ }对其成员变量赋初值,而class则不能,在经典书目《thinking C++ 2nd edition》中作者对此点进行了强调。

4. struct编程注意事项

  看看下面的程序:

1. #include

2. struct structA
3. {
4. int iMember;
5. char *cMember;
6. };

7. int main(int argc, char* argv[])
8. {
9. structA instant1,instant2;
10.char c = 'a';
   
11. instant1.iMember = 1;
12. instant1.cMember = &c;
 
13.instant2 = instant1;
 
14.cout << *(instant1.cMember) << endl;
 
15.*(instant2.cMember) = 'b';
 
16. cout << *(instant1.cMember) << endl;
 
17. return 0;
}

  14行的输出结果是:a
  16行的输出结果是:b

  Why?我们在15行对instant2的修改改变了instant1中成员的值!

  原因在于13行的instant2 = instant1赋值语句采用的是变量逐个拷贝,这使得instant1和instant2中的cMember指向了同一片内存,因而对instant2的修改也是对instant1的修改。

  在C语言中,当结构体中存在指针型成员时,一定要注意在采用赋值语句时是否将2个实例中的指针型成员指向了同一片内存。

  在C++语言中,当结构体中存在指针型成员时,我们需要重写struct的拷贝构造函数并进行“=”操作符重载。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325893027&siteId=291194637