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)参数的使用了。


相关文章
|
IDE 编译器 开发工具
【C语言】全面系统讲解 `#pragma` 指令:从基本用法到高级应用
在本文中,我们系统地讲解了常见的 `#pragma` 指令,包括其基本用法、编译器支持情况、示例代码以及与传统方法的对比。`#pragma` 指令是一个强大的工具,可以帮助开发者精细控制编译器的行为,优化代码性能,避免错误,并确保跨平台兼容性。然而,使用这些指令时需要特别注意编译器的支持情况,因为并非所有的 `#pragma` 指令都能在所有编译器中得到支持。
1107 41
【C语言】全面系统讲解 `#pragma` 指令:从基本用法到高级应用
|
3月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1023 0
|
11月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
686 23
|
5月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
331 15
|
10月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
430 1
一文彻底搞清楚C语言的函数
|
11月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
593 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
|
11月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
261 24
|
11月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
644 16
|
11月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
532 3
|
11月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
407 2