• C的0长数组以及__attribute__((packed))_


    一、零长数组(另一篇文章参考这里)

    在标准 C 或者 C++ 中由于不支持 0 长度的数组,所以 int array[0]; 这样的定义是非法的。不过有些编译器(如GCC)的扩展功能支持 0 长度的数组。

        在 C 中,0 长度的数组的主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。由于其非标准性,在程序中尽量避免使用 0 长度的数组。作为替换,可以使用 C99 标准中的不完整数组来替换 0 长度的数组定义。如:

            typedef struct _X {
                int a;
                char array[]; //注意,因为是不完整数组,因此不可以计算sizeof(X),无法编译通过
            } X;

        在GNU的gcc-4.4.0的官方指南中的5.14节Arrays of Length Zero一节中,它是如此写道:

            struct line {
                int length;
                char contents[0];
            };

            //...ommit code here

    struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);
            thisline->length = this_length;

        这个用法主要用于变长Buffer,struct line的大小为4,结构体中的contents[0]不占用任何空间,甚至是一个指针的空间都不占(待会儿下面的示例代码会验证),contents在这儿只是表示一个常量指针,这个特性是用编译器来实现的,即在使用 thisline->contents的时候,这个指针就是表示分配内存地址中的某块buffer,比如malloc (sizeof (struct line) + this_length)返回的是0x8f00a40,thisline->contents指向的位置就是(0x8f00a40 + sizeof(struct line)),而这儿sizeof(struct line)仅仅是一个int的四字节。

        对于这个用法,我们定义的结构体指针可以指向任意长度的内存buffer,这个技巧在变长buffer中使用起来相当方便。可能有朋友说,为什么不把最后的contents直接定义为一个指针呢?这儿的差别是这样的,如果定义为一个指针,它需要占用4Bytes,并且在申请好内存后必须人为赋地址才可以。如果使用这个用法,这个常量指针不占用空间,并且无需赋值。

        但是,方便并不是绝对的,在释放分配的内存的时候,由于函数free会认为*thisline 只是指向一个4字节的指针,即只会释放length的空间,而对于后面占据大头的buffer却视而不见,这个就需要人为干预;而对于后面的声明指针的方式,则可以直接用Free(thisline->contents)的方式释放掉分配的内存。(这地方说的不明白,让我理解之后,感觉此话是错误的。)

        如果将零长数组array换成指针*array来使用的话,指针必须重新分配一段内存之后才能使用,那么当想要用socket发送结构体指针的时候,并不会将指针array申请的内存发送过去,因为是不连续的,所以接受socket发送来的数据后,会发现该数据并不是自己想要的

        ASSERT:除非必要,不要轻易使用这个功能,GNU C下可以编译通过,所以你在使用vc++,那就不用尝试了,编译都无法通过。

    C语言: 验证0长数组和__attribute__((packed))

    01 #include <stdio.h>
    02 #include <stdlib.h>
    03 #include <string.h>
    04
    05 #define offsetof(S,t)     (size_t)&(((S *)0)->t)    //求结构体中偏移量的宏,C++中存在此宏,C中需自己定义
    06
    07 struct zero_arry_t{
    08 unsigned int i;
    09 char arry[];
    10 };
    11
    12 struct Test{
    13 int len;
    14 char content[0];
    15 };
    16
    17 typedef struct _S1{
    18 char a;
    19 char b;
    20 double c;
    21 }S1;
    22
    23 typedef struct _S2{
    24 char a;
    25 char b;
    26 double c;
    27 }__attribute__((packed)) S2;    //__attribute__((packed))的作用就是告诉编译器取消结构在编译过程中的优化对齐
    28
    29 typedef struct _Y
    30 {
    31 int a;
    32 int b;
    33 char c;
    34 char content[0];
    35 } Y;
    36
    37
    38 int main()
    39 {
    40 //验证0长度数组   
    41 char c0 = 'a', c1 = 'b', c2='c', c3='d';
    42 printf("c0=%c, c1=%c, c2=%c, c3=%c\n&c0=%p, &c1=%p, &c2=%p, &c3=%p\n", c0, c1, c2, c3, &c0, &c1, &c2, &c3);                          
    43 struct Test t;
    44 t.len = 0x01020304;
    45 char *q = t.content;
    46 printf("sizeof(t)=%u, sizeof(t.content)=%u\n", sizeof(t),sizeof(t.content));    //打印4 0, content本身不占空间
    47 printf("&t=%p, &t.len=%p, t.content=%p, &t.content=%p\n", &t, &t.len, t.content, &t.content);//
    48 strcpy(t.content, "123");
    49 //发现 c0 c1 c2 c3的位置的内容被p->content所修改
    50 printf("c0=%c, c1=%c, c2=%c, c3=%c\n&c0=%p, &c1=%p, &c2=%p, &c3=%p\n", c0, c1, c2, c3, &c0, &c1, &c2, &c3);
    51
    52 char buf[1024] = {0};
    53 struct Test *p = (struct Test *)buf;
    54     p->len = 0x01020304;
    55 strcpy( p->content, "abcd");
    56 printf("\np=&buf=%p, p->content=%p, p->content=%s\n", buf, p->content, p->content);
    57 int k;
    58 for(k=0; k<10; ++k)    //注意观察这十个位置的值
    59 printf("address %p: buf[%d]=%d\n", buf+k, k, buf[k]);
    60
    61 //关于offsetof宏,以及 __attribute__((packed))属性
    62 printf("\nsizeof(S1)=%u, offsetof(S1,c)=%u\n", sizeof(S1),offsetof(S1,c));
    63 //使用__attribute__后,结构体大小和成员的偏移量都发生变化
    64 printf("sizeof(S2)=%u, offsetof(S2,c)=%u\n", sizeof(S2),offsetof(S2,c));
    65 //对于有padding(补齐)的结构体Y,其sizeof(Y)和offsetof(Y, content)的大小不一致,参考这个帖子
    66 printf("sizeof(Y)=%u, offsetof(Y, content)=%u, offsetof(Y, c)=%u\n", sizeof(Y), offsetof(Y, content), offsetof(Y, c));
    67
    68 getchar();
    69 return 0;
    70 }

        运行结果截图如下:

    image

    二、__attribute__

        1. __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,用vc的编译器也不是紧凑的,用tc的编译器就是紧凑的。例如:

        在GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
        在GCC下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5

        2. __attribute__关键字主要是用来在函数或数据声明中设置其属性。给函数赋给属性的主要目的在于让编译器进行优化。函数声明中的 __attribute__((noreturn)),就是告诉编译器这个函数不会返回给调用者,以便编译器在优化时去掉不必要的函数返回代码。

        GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。

        __attribute__书写特征是:__attribute__前后都有两个下划线,并且后面会紧跟一对括弧,括弧里面是相应的__attribute__参数。

        __attribute__语法格式为:

        __attribute__ ((attribute-list))

        其位置约束:放于声明的尾部“;”之前。

        函数属性(Function Attribute):函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容之功效。

        GNU CC需要使用 –Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。

        packed属性:使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
        举例:
            /* __attribute__ ((packed)) 的位置约束是放于声明的尾部“;”之前 */
            struct str_struct{
                __u8    a;
                __u8    b;
                __u8    c;
                __u16   d;
            } __attribute__ ((packed));

            /* 当用到typedef时,要特别注意__attribute__ ((packed))放置的位置,相当于:
             * typedef struct str_stuct str;
             * 而struct str_struct 就是上面的那个结构。
            */
            typedef struct {
                __u8    a;
                __u8    b;
                __u8    c;
                __u16   d;
            } __attribute__ ((packed)) str;

            /* 在下面这个typedef结构中,__attribute__ ((packed))放在结构名str_temp之后,其作用是被忽略的,注意与结构str的区别。*/
            typedef struct {
                __u8    a;
                __u8    b;
                __u8    c;
                __u16   d;
            }str_temp __attribute__ ((packed));    //这样不起作用,还可能编译不通过

  • 相关阅读:
    maven 仓库配置 pom中repositories属性
    Spring Boot集成持久化Quartz定时任务管理和界面展示
    gradle使用总结
    sqlserver 分页
    MyBatis特殊字符转义
    Mybatis中#{}和${}传参的区别及#和$的区别小结
    Markdown 手册
    Spring boot——logback.xml 配置详解(四)<filter>
    Spring boot——logback.xml 配置详解(三)<appender>
    Spring boot——logback.xml 配置详解(二)
  • 原文地址:https://www.cnblogs.com/dongzhiquan/p/1986614.html
Copyright © 2020-2023  润新知