C语言中 attribute 的意义,添加#pragma pack(1)的使用。

简介: C语言中 attribute 的意义,添加#pragma pack(1)的使用。

C语言中 attribute 的意义

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


  • __attribute__书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
  • __attribute__语法格式为:attribute ((attribute-list))
  • 其位置约束为:放于声明的尾部“;”之前。

关键字__attribute__也可以对结构体(struct)或共用体(union)进行属性设置。大致有六个参数值可以被设定,即:aligned, packed, transparent_union, unused, deprecated和may_alias。


  • 在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。

aligned (alignment)

  • 该属性设定一个指定大小的对齐格式(以字节 为单位)
struct S {
short b[3];
} __attribute__ ((aligned (8)));
typedef int int32_t __attribute__ ((aligned (8)));


  • 该声明将强制编译器确保(尽它所能)变量类 型为struct S或者int32_t的变量在分配空间时采用8字节对齐方式。
  • 如上所述,你可以手动指定对齐的格式,同 样,你也可以使用默认的对齐方式。如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如:
struct S {
short b[3];
} __attribute__ ((aligned));


packed

  • 使用该属性对struct或者union类型进行定义,设定其类型的每一个变量的内存约束。当用在enum类型 定义时,暗示了应该使用最小完整的类型(it indicates that the smallest integral type should be used)。
  • 下面的例子中,packed_struct类型的变量数组中的值将会紧紧的靠在一起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,unpacked-struct也需要使用packed进行相应的约束。
struct unpacked_struct{
    char c;      
    int i;
};
struct packed_struct{     
    char c;     
    int  i;     
    struct unpacked_struct s;
}__attribute__ ((__packed__));


函数属性(Function Attribute)

  • 函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容之功效。
  • GNU CC需要使用 –Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。下面介绍几个常见的属性参数。

attribute format

  • 该__attribute__属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。

constructor/destructor

  • 若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。
  • 类似的,若函数被设定为destructor属性,则该 函数会在main()函数执行之后或者exit()被调用后被自动的执行。
  • 拥有此类属性的函数经常隐式的用在程序的初始化数据方面。这两个属性还没有在面向对象C中实现。同时使用多个属性可以在同一个函数声明里使用多个__attribute__,并且实际应用中这种情况是十分常见的。
  • 使用方式上,你可以选择两个单独的__attribute__,或者把它们写在一起,可以参考下面的例子:
#include <stdio.h>
static __attribute__((constructor))voidbefore(){
    printf("Hello\n");
}
static __attribute__((destructor))voidafter(){
    printf(" World!\n");
}
int main(int argc, char** argv){
    printf(" >>>>>>\n");
    return 0;
}


  • 输出:
Hello
>>>>>>
World!


关键字 visibility 定义库中变量或函数是否库外可见。

  • attribute((visibility(“hidden”))) void fun1();
  • attribute((visibility(“default”))) void fun2();

visibility用于设置动态链接库中函数的可见性,将变量或函数设置为hidden,则该符号仅在本so中可见,在其他库中则不可见。


g++在编译时,可用参数-fvisibility指定所有符号的可见性(不加此参数时默认外部可见,参考man g++中-fvisibility部分);若需要对特定函数的可见性进行设置,需在代码中使用__attribute__设置visibility属性。


编写大型程序时,可用-fvisibility=hidden设置符号默认隐藏,针对特定变量和函数,在代码中使用__attribute__ ((visibility(“default”)))另该符号外部可见,这种方法可用有效避免so之间的符号冲突。


示例:


  • visibility1.cpp
//visibility1.cpp
#include <stdio.h>
extern "C" void fun1(){
    printf("in %s\n", __FUNCTION__);
}
__attribute__((visibility("default"))) void fun1();//当在编译时使用 -fvisibility=default 参数时可以不用这行显式调用,如果添加这行,则编译参数无效,但是对该文件其他变量和函数会有影响。


  • visibility2.cpp
//visibility2.cpp
#include <stdio.h>
extern "C" void fun1();
extern "C" void fun2();
void fun2(){
    fun1();
    printf("in %s\n", __FUNCTION__);
}
__attribute__((visibility("default"))) void fun2();同上


  • main.cpp
//main.cpp
#include <stdio.h>
extern"C" void fun1();
extern"C" void fun_3();
extern"C" void fun2();
int main(){
    fun1();
    fun2();
    fun_3();
    return 0;
}


  • makefile
//makefile
//-Wl,-rpath=. 传递编译器参数,指定动态库路径
all:test
test: main.o libtest.so
  g++ -o test main.o -ltest -L. -Wl,-rpath=.
main.o: main.cpp
  g++ -c main.cpp  -I . -ltest -L.
libtest.so: visbility1.o visbility2.o
  g++ -o libtest.so visbility1.o visbility2.o --shared -fPIC
visbility1.o:visbility1.cpp
  g++ -c visbility1.cpp --shared -fPIC -fvisibility=default
visbility2.o:visbility2.cpp
  g++ -c visbility2.cpp --shared -fPIC -fvisibility=default
clean:
  rm -f *.o *.so test


  • 尝试变换__attribute__((visibility(“default”))) void fun2();中的"default" 和"hidden"、“protected” or "internal"查看变换。
  • visibility为声明该内容是库内部可见还是外部引用,是属于大型项目用于避免命名重复的方式。

定义结构体内存对齐的大小

  • 当我们不设定内存对齐的大小时,编译器会自动根据结构体内的数据类型定义对齐大小。
  • 例如:
typedef struct huhu{
    char data_c;
    int data_i;
    double dota_d;
}huhu_t;
typedef struct aa{
    union{
        huhu_t b;
        char buf[sizeof(b)];
    }cc;
    int ada;
}aa_t;
int main(){
    printf("huhu_t sizeof = %d\n", sizeof(huhu_t));
    printf("aa_t sizeof = %d\n", sizeof(aa_t));
    return 0;
}



结果是


ww@ubuntu:~/demo$ ./a.out 
huhu_t sizeof = 16
aa_t sizeof = 24


  • 此时,内存对齐的大小在struct huhu中是4,在struct aa中是8,再修改一下代码如下:
#include<iostream>
using namespace std;
typedef struct huhu{
    double dota_d;
    double data_i;
    char data_c;
}huhu_t;
typedef struct aa{
    union{
        huhu_t b;
        char buf[sizeof(b)];
    }cc;
    int ada;
}aa_t;
int main(){
    printf("huhu_t sizeof = %d\n", sizeof(huhu_t));
    printf("aa_t sizeof = %d\n", sizeof(aa_t));
    return 0;
}


  • 此时,程序输出为:
ww@ubuntu:~/demo$ ./a.out 
huhu_t sizeof = 24
aa_t sizeof = 32


  • 可见,此时struct huhu中的内存对齐大小为8,struct aa也是8。

添加【#pragma pack(1)】在代码开头来设定对齐大小

代码如下:


#pragma pack(1)
#include<iostream>
using namespace std;
typedef struct huhu{
    char data_c;
    int dota_d;
    double data_i;
}huhu_t;
typedef struct aa{
    union{
        huhu_t b;
        char buf[sizeof(b)];
    }cc;
    int ada;
}aa_t;
int main(){
    printf("huhu_t sizeof = %d\n", sizeof(huhu_t));
    printf("aa_t sizeof = %d\n", sizeof(aa_t));
    return 0;
}


  • 结果输出为:
ww@ubuntu:~/demo$ ./a.out 
huhu_t sizeof = 13
aa_t sizeof = 17


  • 可见huhu_t的大小其实是 1 + 4 + 8 = 13 这样来的,而aa_t则是由前面的13 + 4 = 17算出来的,此时对齐大小就是1了。
  • 如果再修改为2的话:
#pragma pack(2)
#include<iostream>
using namespace std;
typedef struct huhu{
    char data_c;
    int dota_d;
    double data_i;
}huhu_t;
typedef struct aa{
    union{
        huhu_t b;
        char buf[sizeof(b)];
    }cc;
    int ada;
}aa_t;
int main(){
    printf("huhu_t sizeof = %d\n", sizeof(huhu_t));
    printf("aa_t sizeof = %d\n", sizeof(aa_t));
    return 0;
}


  • 结果为:
ww@ubuntu:~/demo$ ./a.out 
huhu_t sizeof = 14
aa_t sizeof = 18


  • 由此可见具体pack(n)参数的使用了。


相关文章
|
6月前
|
编译器
头文件ifndef用法及意义#pragma once
头文件ifndef用法及意义#pragma once
58 0
|
6月前
|
编译器 程序员 C++
[C++] #ifndef和#define与#pragma once在头文件中的作用和关系
[C++] #ifndef和#define与#pragma once在头文件中的作用和关系
93 0
|
编译器 C语言
宏、条件编译(#ifdef)、#include(头文件包含)、#error和 #pragma的区别、#和##的含义和应用
宏、条件编译(#ifdef)、#include(头文件包含)、#error和 #pragma的区别、#和##的含义和应用
106 0
|
存储 编译器
#Pragma Pack(n)内存分配
#Pragma Pack主要是用在字节对齐方面,为什么要对齐呢? 因为计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
137 0
#Pragma Pack(n)内存分配
|
缓存 算法 编译器
C语言编译器Parser和CodeGen的过程(中)
C语言编译器Parser和CodeGen的过程(中)
207 0
C语言编译器Parser和CodeGen的过程(中)
|
存储 自然语言处理 算法
C语言编译器Parser和CodeGen的过程(下)
C语言编译器Parser和CodeGen的过程(下)
233 0
C语言编译器Parser和CodeGen的过程(下)
|
自然语言处理 算法 编译器
C语言编译器Parser和CodeGen的过程(上)
C语言编译器Parser和CodeGen的过程(上)
256 0
C语言编译器Parser和CodeGen的过程(上)
#pragma的一些用法
1、#pragma message message 参数:Message参数能够在编译信息输出窗口输出相应的信息,这对于源代码的信息控制特别重要,其使用方法为: #pragma message("消息文本") 当我们程序中定义了许多宏来控制源代码版本的时候,我们自己都有可能会忘记有没有正确设置...
1116 0