精华内容
下载资源
问答
  • C语言中结构体的剖析

    2016-08-22 15:14:19
    写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?...原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据
    写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢? 结构体到底怎样对齐?

    【结构体】

    有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下前人的经验(在没有#pragma pack宏的情况下):


    原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
    原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
    原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。


    这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。

     

     例1:struct{

      short a1;
      short a2;
      short a3;
      }A;


      struct{
      long a1;
      short a2;
      }B;


      sizeof(A) = 6; 这个很好理解,三个short都为2。
      sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。


      例2:struct A{
      int a;
      char b;
      short c;
      };


      struct B{
      char b;
      int a;
      short c;
      };


    struct C{
      char b;
      short c;
      int a;
      };


      sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。
      sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。
      sizeof(C) = 8; char为1,short为2,int为4,这里用到了原则1和原则3。

      深究一下,为什么是这样,我们可以看看内存里的布局情况。


      a b c
      A的内存布局:1111, 1*, 11


      b a c
      B的内存布局:1***, 1111, 11**

      b c a

    B的内存布局:1*,11, 1111

      其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。


      B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

      再看一个结构中含有结构成员的例子:

      例3:struct A{
      int a;
      double b;
      float c;
      };


      struct B{
      char e[2];
      int f;
      double g;  
      short h;
      struct A i;
      };


      sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。
      sizeof(B) = 48; 看看B的内存布局。


      e f g h i 
      B的内存布局:11* *, 1111, 11111111, 11 * * * * * *, 1111* * * *, 11111111, 1111 * * * *  
      i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。把B的内存布局弄清楚,有关结构体的对齐方式基本就算掌握了。


      以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;

    首先我们看下#pragma pack的意义:设置结构体的边界对齐为1个字节,也就是所有数据在内存中是连续存储的。


      a b c
      A的内存布局:1111, 11111111, 1111


      e f g h i 
      B的内存布局:11, 1111, 11111111, 11 , 1111, 11111111, 1111  


    【位结构体】


      还有一种常见的情况,结构体中含位域字段。
    位域成员不能单独被取sizeof值。C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。


      使用位域的主要目的是压缩存储,其大致规则为: 
      1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
      2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
      3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式; 
      4) 如果位域字段之间穿插着非位域字段,则不进行压缩; 
      5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。


      还是让我们来看看例子。

      相邻位域相同:
      例4:struct A{ 
      char f1 : 3; 
      char f2 : 4; 
      char f3 : 5; 

      }; 


      a b c
      A的内存布局:111, 1111 *, 11111 * * *
      位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。


      相邻位域不同:

      例5:struct B{ 
      char f1 : 3; 
      short f2 : 4; 
      char f3 : 5; 
      };
      由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。


      相邻位域有非位域穿插:

      例6:struct C{ 
      char f1 : 3; 
      char f2; 
      char f3 : 5; 
      };
      非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。


        考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。


      最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。


    位结构体
    位结构 节省存贮空间 “:”操作符 位域


    一、首先说概念:


    位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构比按位运算符更加方便。
    位结构定义的一般形式为:
    struct  位结构名{
    数据类型 [变量名]: 整型常数; //成员称为“位域”或者“位段”
    数据类型 [变量名]: 整型常数;
    } 位结构变量;


    其中: 数据类型必须是整型(int/char/short)。 整型常数的范围是数据类型的长度, 如定义为short,则范围是1~16。
    变量名是选择项, 可以不命名, 这样规定是为了排列需要。
    例如: 下面定义了一个位结构。
    struct webpage{
    unsigned char incon: 8; /*incon占用低字节的0~7共8位*/
    unsigned char txcolor: 4;/*txcolor占用高字节的0~3位共4位*/
    unsigned char bgcolor: 3;/*bgcolor占用高字节的4~6位共3位*/
    unsigned char blink: 1; /*blink占用高字节的第7位*/
    }ch;
    printf("%d\n",sizeof(struct webpage));输出:2。
    位结构成员的访问与结构成员的访问相同。
    例如: 访问上例位结构中的bgcolor成员可写成:
    ch.bgcolor


    注意:
    1. 一个位域必须存储在定义它的一个数据类型内部,不能跨跨该数据类型。如char定义的位域所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
    struct bs
    {
     unsigned a:4
     unsigned :0 /*空域*/
     unsigned b:4 /*从下一单元开始存放*/
     unsigned c:4
    }
    在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。


    2.由于位域不允许越过定义它的数据类型,因此位域的长度不能大于定义它的数据类型的长度。
    3. 位结构总长度(位数), 是各个位成员定义的位数之和再向最大结构成员对齐。
    4. 位结构成员可以与其它结构成员一起使用。

    例如:
    struct info{
    char name[8];
    int age;
    struct addr address;
    float pay;
    unsigned char state: 1;
    unsigned char pay: 1;
    }workers;
    上例的结构定义了关于一个工人的信息。其中有两个位结构成员, 每个位结构成员只有一位, 用unsigned char数据类型,因此只占一个字节但保存了两个信息, 该字节中第一位表示工人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。


    二、再说说位结构的位域存储顺序问题


    我们知道字节存储顺序有高字节优先的big-endian大端存储法(高字节数据放在低字节地址处)和低字节优先的little-endian小端存储法,无论使用大端法还是小端法,都不存在技术原因,只是涉及到处理器厂商的立场和习惯。INTEL的X86平台使用小端法,IBM、 Motorola、Sun Microsystem的大多数微处理器则使用大端法,还有部分微处理器可以由用户自己设置是使用大端法还是小端法,如ARM、MIPS、PowerPC 等。
    位域在存储时的顺序和它的编译器有关,一般是先申请的放在低位。程序举例如下:
    #i nclude "stdio.h"
    void main()
    {
     union
     {
     struct student
     {
      unsigned char s1:1;
       unsigned char s2:3;
     }x;
     unsigned char c;
     }v;
     v.c=0;
     v.x.s1=0;
     v.x.s2=4;
    printf("%d\n",v.c);
    printf("%d\n",sizeof(struct student)); 
    }
    输出:
    8
    1


    即结构体成员申请是按照顺序从低地址开始。所以上边结构体v在内存中数据的排列顺序为


        s1 s2
        |0| 0|0|1| 0|0|0|0| (1个字节,因为是unsigned char类型)
    低地址       高地址




    s1放着0
    s2放着4(二进制100),在内存里由低到高为“|0|0|1|”。
    所以v.c为二进制00001000,即十进制8。
    同时,因为s1占一个位,s2占三个位,而两者都是unsigned char型,且最大的数据类型也就是unsigned char型,一个字节足够放下s1和s2了。所以我们看到struct student的大小为1个字节。
    如果从先申请的放在高字节,则上边的输出为
               s2  s1
    0000 |001 |0
    即输出应该是:
    64
    1
    网上有人说TURBO C是采用这种方式,我没试过。


    三、位结构的位对齐问题
    位结构的其实不存在位对齐问题,即位不需要对齐。其他方面,位结构和一般结构体类似,遵循结构体的对齐原则,


    #i nclude "stdio.h"


    void main()
    {
     union
     {
     struct student
     {
      unsigned char s1:1;
       unsigned char s2:2;
      unsigned char s3:2;
     }x;
     unsigned char c;
     }v;
     v.c=0;
     v.x.s1=0;
     v.x.s3=2;
    printf("%d\n",v.c);
    printf("%d\n",sizeof(struct student)); 
    }
    输出结果是:
    16
    1


    因为它只按整体对齐,所以为
    s1s2s3
    0  0001000
    即二进制00010000等于十进制16,而不是
    s1s2    s3
    0  00 0 01 00


    再举一个位结构体的例子


    #i nclude "stdio.h"
    void main()
    {
     union
     {
     struct student
     {
      unsigned char s1:1;
       unsigned char s2:2;
      unsigned short s3:2;
     }x;
     unsigned short c;
     unsigned int d;
     }v;
     v.d=0;
     v.x.s1=0;
     v.x.s3=2;
    printf("%d\n",v.d);
    printf("%d\n",sizeof(struct student)); 
    }
    输出为:
    131072
    4


    131072=(10 00000000 00000000)b
    因为遵循结构体对齐原则,s3跳过了2个字节。
    s1s2                               s3
    0  00 00000| 00000000| 01
    展开全文
  • 在准备下周一要发表RAW套接字PPT,准备了好一段时间,看了挺多代码,才发现,一直在准备套接字生成和数据发送接受,就没怎么引用到 RAW.C 里面内容, 都是因为网上没有介绍RAW套接字内核分析文章,看...

    在准备下周一要发表的RAW套接字的PPT,准备了好一段时间,看了挺多代码,才发现,一直在准备套接字的生成和数据发送接受,就没怎么引用到 RAW.C 里面的内容, 都是因为网上没有介绍RAW套接字内核分析的文章,看的文章都是介绍一般的TCP, UDP套接字内核分析,结果我就被引到了别的地方,还全然不知 。。。

     

    仔细看了一下,在 inet_protosw 结构体中 关于 SOCK_RAW部分,有个 ops 取的是 inet_sockraw_ops 结构体的地址, 而 inet_sockraw_ops 中 sendmsg, recvmsg 挂钩的函数是 inet_sendmsg, inet_recvmsg, 我就以为 RAW 套接字就是用它们来发送接收数据了,但是发现在 ops 的上面还有个 prot的成员,它指向的是 raw_prot的结构体,这个结构体的声明在这里: http://lxr.linux.no/linux-old+v2.4.31/net/ipv4/raw.c#L685 贴出来如下:

    685 struct proto raw_prot = {
    686        name:           "RAW",
    687        close:          raw_close,
    688        connect:        udp_connect,
    689        disconnect:     udp_disconnect,
    690        ioctl:          raw_ioctl,
    691        init:           raw_init,
    692        setsockopt:     raw_setsockopt,
    693        getsockopt:     raw_getsockopt,
    694        sendmsg:        raw_sendmsg,
    695        recvmsg:        raw_recvmsg,
    696        bind:           raw_bind,
    697        backlog_rcv:    raw_rcv_skb,
    698        hash:           raw_v4_hash,
    699        unhash:         raw_v4_unhash,
    700};
    这里面挂载的基本上都是 raw.c 里面的函数了,呼,%@¥&*# 在灯火阑珊处。。。。

    转载于:https://www.cnblogs.com/jinrize/archive/2009/11/27/1611716.html

    展开全文
  • 当基本数据类型作为参数传递时,传递是实参值副本,即传是值,无论在函数中怎么操作这个副本,实参值是不会被改变。 当对象作为参数传递时:实际上传递是一份“引用的拷贝”。 引用,可以理解为指针,在...

    1. JAVA

    基本数据类型:作为值传递,改变形参不会影响实参。当基本数据类型作为参数传递时,传递的是实参值的副本,即传的是值,无论在函数中怎么操作这个副本,实参的值是不会被改变的。

    当对象作为参数传递时:实际上传递的是一份“引用的拷贝”。
    引用,可以理解为指针,在主函数中是实参;
    引用的拷贝,传递给子函数,是形参;
    所以引用在传递给子函数时,是copy版的原指针(对象的地址),不是真正的实参。
    当实参和形参指向同一对象时,通过形参修改对象可以影响主函数中的对象;
    但是当形参在子函数中被重新指向另一对象(修改了形参即copy版的原指针),再通过形参修改对象就不会影响到主函数中的对象了!

    Examples:

    public class test {
        public static void main(String[] args) {
            StringBuffer sb = new StringBuffer("Hello ");
            System.out.println("before change, sb is "+sb.toString());
            change(sb);
            System.out.println("after change, sb is "+sb.toString());
        }
        public static void change(StringBuffer stringBuffer){
            stringBuffer.append("world !");
        }
    }
    

    程序的运行结果:
      
      before change, sb is Hello
      after change, sb is Hello world !

    public class test {
        public static void main(String[] args) {
            StringBuffer sb = new StringBuffer("Hello ");
            System.out.println("before change, sb is "+sb.toString());
            change(sb);
            System.out.println("after change, sb is "+sb.toString());
        }
        public static void change(StringBuffer stringBuffer){
            stringBuffer = new StringBuffer("Hi ");
            stringBuffer.append("world !");
        }
    }
    

    运行程序后,结果:
      
      before change, sb is Hello
      after change, sb is Hello

    2. C

    一是传递结构体变量,这是值传递,二是传递结构体指针,这是地址传递,三是传递结构体成员,当然这也分为值传递和地址传递。以传值方式传递结构需要对整个结构做一份拷贝。

    结构体变量的值传递:
    函数的形参直接copy实参结构体,所以在函数内改变形参并不会影响主函数中的结构体。

    结构体变量的地址传递
    传进去函数的形参是指针,即对象的地址。定义函数时,func(*Q);在main中定义一个对象Q,调用函数时,func(&Q)。

    Examples:
    1.下面传递结构体变量

    #include<stdio.h>
    #include<string.h>
    #define format "%d\n%s\n%f\n%f\n%f\n"
    struct student
    {
        int num;
        char name[20];
        float score[3];
    };
    void change( struct student stu );
    int main()
    {
        
        struct student stu;
        stu.num = 12345;
        strcpy(stu.name, "Tom");
        stu.score[0] = 67.5;
        stu.score[1] = 89;
        stu.score[2] = 78.6;
        change(stu);
        printf(format, stu.num, stu.name, stu.score[0], stu.score[1],stu.score[2]);
        printf("\n");
        return 0;
    }
     
    void change(struct student stu)
    {
        stu.score[0] = 100;
        strcpy(stu.name, "jerry");
    }
    

    可以看到最终输出的值并未改变。。。

    2.地址传递

    #include<stdio.h>
    #define format "%d\n%s\n%f\n%f\n%f\n"
    struct student
    {
        int num;
        char name[20];
        float score[3];
    };
    void change( struct student* stu );
    int main()
    {
        
        struct student stu;
        stu.num = 12345;
        strcpy(stu.name, "Tom");
        stu.score[0] = 67.5;
        stu.score[1] = 89;
        stu.score[2] = 78.6;
        change(&stu);
        printf(format, stu.num, stu.name, stu.score[0], stu.score[1],stu.score[2]);
        printf("\n");
        return 0;
    }
     
    void change(struct student* p)
    {
        p->score[0] = 100;
        strcpy(p->name, "jerry");
    }
    

    可以看到,通过地址传递修改了结构体内的数据

    用&stu做实参,&stu是结构体变量stu的地址。在调用函数时将该地址传送给形参p(p是指针变量)。这样p就指向stu。

    在change函数中改变结构体内成员的值,在主函数中就输出了改变后的值。

    Reference:
    https://blog.csdn.net/xiangwanpeng/article/details/52454479
    https://www.runoob.com/w3cnote/c-the-structure-of-the-parameter.html

    展开全文
  • int CompareElemType(ElemType a, ElemType b){ //比较结构体数据的异同 if (a.sn == b.sn && a.score == b.score && strcmp(a.name, b.name) == 0) return 1; else return 0; 我运行有这个错误,为什么: ...
  • 结构体变量一样,也可以通过“.”引用共用体的数据成员。注意:共用体的各个数据成员共用同一内存资源,如果将共用体某一数据成员修改后,它所在的内存的值会发生变化,相应的这个共用体的其它数据成员也...

    d8f1303b1f50bf9d6195af3347fd6de2.png

    一、共用体

    共用体和结构体有些类似,它们都有多个数据成员。不同的是,结构体的多个数据成员是各自独立分别存放的。

    而共用体的多个数据成员却是保存在内存中的同一个位置,它们“共用”一段内存资源,所以被称为“共用体”。

    同结构体变量一样,也可以通过“.”引用共用体的数据成员。

    注意:共用体的各个数据成员共用同一内存资源,如果将共用体某一数据成员修改后,它所在的内存的值会发生变化,相应的这个共用体的其它数据成员也会发生变化。

    二、枚举

    C语言中的枚举类型,其实质是就是一个Int类型的数据。而所谓的枚举值,不过就是给一些位于某个范围内的整数值(由编译器给定或者程序员在代码中给定)一个容易记忆和使用的标识符(名字)。使用这个有意义的名字给变量赋值,在一定程度上可以提高代码的可读性。

    enum  TraficLight
    {
    Green=1, //给定枚举值
    Rea,
    Yellow
    };
    Int main()
    {
    enum TraficLight  light;
    Light = red;  //为枚举类型赋值
    return 0;
    }
    枚举值通常表达的是某一个范围内的有限的可选值,且枚举值可以作为常量使用,所以往往使用枚举值来作为switch语句的分支条件,表达对某一范围内的不同情况的处理。
    switch(light)
    {
    case red:  break;
    case yellow:break;
    case green:break;
    }

    枚举类型的意义在于提高程序代码的可读性。

    举例:关于交通灯枚举编程题目

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    enum TraficLight {
     red, green, yellow
    };
    int main()
    {
     enum TraficLight  light;
     srand(time(NULL));
     int num = rand()%3;
     light =(TraficLight)num;
     switch(light)
     {
      case red : printf("目前是红灯,禁止通行n");break;
      case green: printf("目前是绿灯,可以通行n");break;
      case yellow:printf("目前是黄灯,快速通行n");break;
     }
      return 0;
    }

    三、typedef

    关于typedef,一个关于函数指针,一个关于结构体

    关于函数指针

    #include <stdio.h>
    typedef int (*fun)(int i, int j) ;  //定义一个函数指针新类型fun,返回值为int,参数有2个整型参数
    int add(int a, int b)
    { return a+b; } 
     int sub(int a, int b)
     {  return b-a; }
      int main()
     {  int a,b; fun  f1;
      printf("input a,b:n");
      scanf("%d%d",&a,&b);
       if(a>b) 
      {  f1=add;
       printf("%d+%d=%dn",a,b,f1(a,b));
      }
      else
      {   f1=sub;
       printf("%d-%d=%dn",b,a,f1(a,b));  
      }   
      return 0; }

    关于结构体

    #include <stdio.h>
    typedef struct Student
    { int num;
     char name[20];
     float score;
    } Stu;
    int main()
    {
     Stu s[3]={{1,"er",89},{2,"sd",67},{3,"we",86}},*p;
     int i;
     for(i=0;i<3;i++)
     {
      printf("学号:%dt姓名:%st分数:%fn",s[i].num,s[i].name,s[i].score);
     }
      for(p=s;p<s+3;p++)
      printf("学号:%dt姓名:%st分数:%fn",p->num,p->name,p->score);
     
      return 0;
    }
     
    展开全文
  • 使用常量编码信息(例如一个用于引用管理员权限常量USER_ADMIN_ROLE = 1 )。 使用字符串常量作为字段名在数组使用。 大多数编程语言都支持基本数据类型和结构类型(类、结构体等)。结构类型允许程序员...
  • HAL 库那些结构体怎么与寄存器地址对应起来。这里我们就做一个简要分析吧。首先我们看看 51 怎么。 51 单片机开发经常会引用一个 reg51.h 头文件,看看他是如何把名字和寄存器联系起来:sfr P...
  • 构建对象方法和数组相似,对象也是引用数据类型,只能使用new运算符从堆分配内存;创建对象一般语法:类名 引用名 = new 类名();使用已经定义好类,创建该类对象过程称为“实例化”。在C语言,必须要先...
  • 之所以要讲解这部分知识,是因为经常会遇到客户提到不明白 MDK 那些结构体怎么与寄存器地址对应起来。这里我们就做一个简要分析吧。 首先我们看看 51 怎么。 51 单片机开发经常会引用一个 reg51.h...

空空如也

空空如也

1 2 3 4 5
收藏数 93
精华内容 37
关键字:

怎么引用结构体中的数据