GNU attribute 机制

attribute机制


参考


是什么

One of the best (but little known) features of GNU C is the __attribute__ mechanism, which allows a developer to attach characteristics to function declarations to allow the compiler to
perform more error checking. It was designed in a way to be compatible with non-GNU implementations, and we've been using this for years in highly portable code with very good results.
Note that __attribute__ spelled with two underscores before and two after, and there are always two sets of parentheses surrounding the contents. There is a good reason for this - see below.
Gnu CC needs to use the -Wall compiler directive to enable this (yes, there is a finer degree of warnings control available, but we are very big fans of max warnings anyway).

  • GNU C 的一个著名的特性是 __attribute__ 机制,它的作用是允许开发者在函数声明的时候,指定一些特性的编译器指令,它可以让编译器在编译的时候进行更多的错误检查和高级优化工作。

语法格式

主要描述 __attribute__ 属性机制在 C 语言中的语法,C++ 和 Objective-C 一些细节上略有不同。C++ 中,当__attribute__ 属性机制与重载和模板一起
使用时,可能会出现问题。将来 C++ 对属性的支持可能只局限于声明中的属性,而在嵌套声明器中不支持 。

  • __attribute__ 语法格式

    属性说明符(attribute specifier) 的形式为:
        __attribute__ ((attribute-list))
    
    attribute-list 可以是用逗号(,)分隔多个属性
    
    

    attribute 前后各有两条下划线 ,然后后面跟两组括号(两个括号可以让它很容易在宏里面使用,特别是有多个属性的时候)。

  • __attribute__ 指令可以放在函数变量类型等声明之后,分号(; )之前。

  • Gnu CC 需要使用 -Wall 编译器指令来启用它

  • 属性说明符列表可以作为结构体联合体枚举说明符的一部分出现。它可以紧跟在 struct,unionenum 关键字之后, 也可以紧跟在右大括之后(after the closing brace),
    分号之前

    struct S {
    
        short b[3];
    
    } __attribute__ ((aligned (8)));
    
    typedef int int32_t __attribute__ ((aligned (8)));    
    

分类

__attribute__ 指令可以放在函数,变量和类型声明之后
注意,gcc 的不同版本对 __attribute__ 的使用可能有微小的差异,可以对应着 gcc 的版本查阅文档

GCC Function Attributes

  • GNU CC++ 中,您可以使用函数属性来指定某些函数属性,这些属性可以帮助编译器优化调用或更仔细地检查代码的正确性。例如:
    • 可以使用属性来指定函数从不返回(noreturn)
    • 返回值只取决于其参数的值(const)
    • 或具有 printf 风格的参数(format)

GCC Variable Attributes

GCC Type Attributes

GCC also supports other attributes


常用属性

函数属性(Function Attribute) 类型属性(Type Attributes) 变量属性(Variable Attribute) Clang特有的
noreturn aligned alias availability
noinline packed at(address) overloadable
always_inline bitband aligned
flatten deprecated
pure noinline
const packed
constructor weak
destructor weakref(“target”)
sentinel section(“name”)
format unused
format_arg used
section visibility(“visibility_type”)
used zero_init
unused
deprecated
weak
malloc
alias
warn_unused_result
nonnull
nothrow (不抛出C++ 异常)

指定函数属性

__attribute__ unused

  • 功能

    • 标记函数或者是变量可能在定义声明后不使用,编译器不会发出未使用变量的警告
    • 告诉人们目的:这是有意不使用的。
  • 使用示例

    /* warning: 'someFunction' declared 'static' but never defined */
    static int someFunction() __attribute__((unused));
    
    int main(int argc __attribute__((unused)), char **argv)
    {
    /* warning: unused variable 'mypid' */
    int	mypid __attribute__((unused)) = getpid();
    
    #ifdef DEBUG
    	printf("My PID = %d\n", mypid);
    #endif
    
    	return 0;
    }    
    

__attribute__ format

  • 功能

    • 可以给被声明的函数加上类似 printf 或者 scanf 的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。
    • format 属性告诉编译器,按照 printf, scanf 等标准 C 函数参数格式规则对该函数的参数进行检查。这在我们自己封装调试信息的接口时非常的有用。
    • 注意,“标准”库函数——printf 之类的函数——在默认情况下已经被编译器理解了。
  • format 的语法格式为:

    format (archetype, string-index, first-to-check)
        
    其中:
        “archetype”指定是哪种风格;
        “string-index”指定传入函数的第几个参数是格式化字符串;
        “first-to-check”指定从函数的第几个参数开始按上述规则进行检查。
        
    有两种风格,第一种风格用得更多:
        __attribute__((format(printf,m,n)))
        __attribute__((format(scanf,m,n)))
    
        
    其中参数 m 与 n 的含义为:
        m:自定义函数声明的第几个参数为格式化字符串(format string);
        n:自定义函数声明的第一个可变参数的位置,即参数“…”里的第一个参数在函数参数总数排在第几。
    
  • 使用示例

    /* 该函数的,参数类似printf(),但只输出到标准错误*/
    extern void eprintf(const char *format, ...)
    	__attribute__((format(printf, 1, 2)));  /* 1=format 2=params */
    
    /*该函数只有在调试达到所需级别时才打印*/
    extern void dprintf(int dlevel, const char *format, ...)
    	__attribute__((format(printf, 2, 3)));  /* 2=format 3=params */    
    
    
    /*dpinger 源码中的 fatal 输出 */
    __attribute__ ((noreturn, format (printf, 1, 2)))
    static void fatal(const char * format, ...)
    {
        va_list args;
    
        va_start(args, format);
        vfprintf(stderr, format, args);
        va_end(args);
    
        exit(EXIT_FAILURE);
    }    
    

__attribute__ noreturn

  • 功能

    • 该属性告诉编译器该函数永远不会返回,这可以用来抑制关于未到达代码路径的错误(比如函数要求返回值,但中间满足了某个退出条件直接退出了)。
    • 一旦以这种方式标记,编译器就可以跟踪代码的路径,并抑制由于控制流在函数调用后永远不会返回而永远不会发生的错误。
  • 示例

    • C 库函数 abort()exit() 都使用这个属性声明:

      extern void exit(int)   __attribute__((noreturn));
      extern void abort(void) __attribute__((noreturn));
      
    • 在这个例子中,两个几乎相同的 C 源文件引用了一个从不返回的 "exitnow()" 函数,但是如果没有 __attribute__ 标记,编译器会发出警告。编译器在这里是正确的,
      因为它无法知道控件没有返回。

      /*$ cat test1.c*/
      extern void exitnow();
      
      int foo(int n)
      {
          if ( n > 0 )
      	{
              exitnow();
              /* control never reaches this point */
      	}
          else
              return 0;
      }
      
      // 编译输出结果
      // $ cc -c -Wall test1.c
      //test1.c: In function ‘foo’:
      //test1.c:12:1: warning: control reaches end of non-void function [-Wreturn-type]}    
      
      /*添加 __attribute__((noreturn)) 后,编译输出无警告*/
      /*$ cat test2.c*/
      extern void exitnow() __attribute__((noreturn));
      
      int foo(int n)
      {
              if ( n > 0 )
                      exitnow();
              else
                      return 0;
      }
      
      $ cc -c -Wall test2.c
      

__attribute__ const

  • 功能

    • 这主要是为了编译器优化对一个函数的重复调用,编译器知道重复调用某些具有相同参数值的函数将重复返回相同的值。 编译器可以选择只调用函数一次,并缓存返回值。
    • const属性修饰的函数与用 pure 属性修饰的十分类似,不过 const 属性比 pure 更严格,它要求函数不能读全局对象(全局变量)。
  • 注意

    • 具有指针参数并检查所指向数据的函数不能被声明为 const
    • 调用非 const 函数的函数通常不能为 const
    • const 函数返回 void 是没有意义的。
    • const 属性禁止函数在连续两次调用之间读取影响其返回值的对象。
  • 示例:在这个高度人为的例子中,编译器通常必须在每个循环中调用 square() 函数,即使我们知道它每次返回的值都是相同的:

    extern int square(int n) __attribute__((const));
    
    ...
    	for (i = 0; i < 100; i++ )
    	{
    		total += square(5) + i;   // square(5) 每次都返回相同的值,通过添加__attribute__((const)),告诉GCC,对于具有相同参数值的 square 函数,后续调用可以
    		                          // 被第一次调用的结果所取代,而不管它们之间的语句是什么,编译器可以选择只调用函数一次,并缓存返回值。
    	}    
    

__attribute__ nonnull (arg-index, ...)

  • 功能:
    • nonnull 属性指定一些函数形参应该是非空指针
    • nonnull 属性可应用于具有至少一个指针类型参数的函数。
    • nonnull (arg-index, ...) arg-index ,指针参数在你定义的函数的参数列表中的位置
  • 示例
    extern void *my_memcpy (void *dest, const void *src, size_t len)__attribute__((nonnull (1, 2)));  
    表明,第一个参数,和第二个参数都应该是非空指针
    

指定变量的属性

__attribute__ aligned(alignment)

  • 功能

    • 此属性指定变量或结构字段的最小对齐方式,以字节为单位。
    • alignment 的值只能是 2 的 n 次幂,n 为正整数。最小对其单位为 2 字节。
    • aligned 前后加上下划线,这允许您在头文件中使用这些属性,而不必关心可能同名的宏。例如,你可以使用 __aligned__ 代替 aligned
      __attribute__ ((aligned(8))) 等同于 __attribute__ ((__aligned__(8)))
      
  • 示例

    int x __attribute__ ((aligned (16))) = 0;           // 使编译器在 16 字节边界上分配全局变量x, 即定义变量在内存中的地址总能被 16 整除。占用的空间大小不变
    typedef int int32_t __attribute__ ((aligned (8)));  // 即定义变量在内存中的地址总能被 8 整除,占用的空间大小不变
    
    struct S1 {
    
        short b[3];
        int a;
    
    };// 默认按照 sizeof(int) = 4 对齐
    
    struct S2 {
    
        short b[3];
        int a;
    
    } __attribute__ ((aligned(8))); //按照 8 字节对齐
    printf("struct S1 size: %ld\n",sizeof(struct S1));
    printf("struct S2 size: %ld\n",sizeof(struct S2));
    
    输出:
        struct S1 size: 12
        struct S2 size: 16
        
    
  • 关于内存对齐,参考:C-基础知识-内存对齐

  • 注意:

    • 对齐属性的有效性可能会受到链接器中固有值的限制。在许多系统上,链接器只能安排变量对齐到某个既定的最大对齐值。
    • 对于一些连接器,最大支持的对准值可能非常非常小。如果链接器只能对变量进行 8 字节对齐,那么在 __attribute__ 中指定 aligned(16) 仍然只能为您提供 8 字节对齐。所以具体信息就得查看链接器文档。

__attribute__ packed

  • 功能

    • __attrubte__ ((packed)) 的作用就是告诉编译器取消结构体或者是联合体在编译过程中的优化对齐,按照实际占用字节数进行对齐。当附加到枚举定义时,它表示应该使用最小整型。
  • 示例1

    struct S2 {
    
        short b[3];
        int a;
    
    } __attribute__ ((aligned (8))); // 或者用:__attribute__ ((__aligned__(8)))
    
    struct S3 {
    
        short b[3];
        int a;
    
    } __attribute__ ((packed));      // 或者用:__attribute__ ((__packed__))
    
    // 输出:
    //    struct S2 size: 16
    //    struct S3 size: 10
    
    
  • 示例 /usr/include/net/ethernet.h

    /* 10Mb/s ethernet header */
    struct ether_header
    {
        uint8_t  ether_dhost[ETH_ALEN];   /* destination eth addr */
        uint8_t  ether_shost[ETH_ALEN];   /* source ether addr    */
        uint16_t ether_type;              /* packet type ID field */
    } __attribute__ ((__packed__));
    
  • 注意

    • 您只能在枚举、结构或联合的定义上指定此属性,而不能在未定义枚举类型、结构或联合的 typedef 上指定。

    • 内部有结构体成员时:

      struct my_unpacked_struct
      {
        char c;
        int i;
      };
      
      struct my_packed_struct __attribute__ ((__packed__))
      {
         char c;
         int  i;
         struct my_unpacked_struct s;
      };        
      上面的定义中: struct my_packed_struct 的每个成员间是没有空隙的,sizeof(struct my_packed_struct) = sizeof(char) + sizeof(int) + sizof(struct my_unpacked_struct)
      但是对于成员 s 即 struct my_unpacked_struc 的内部,并没有压缩内存。如果要压缩,就得在定义 struct my_unpacked_struc 时使用 packed 属性。
      
      
    • 当用到 typedef 时,要特别注意 __attribute__ ((packed)) 放置的位置

      typedef struct{
          int         a;
          char        b;
          long        c;
          long double d;
      } __attribute__ ((packed)) str_p_t1;  // 有效 packed
      
      typedef struct{
          int         a;
          char        b;
          long        c;
          long double d;
      }str_p_t2 __attribute__ ((packed));   // 无效 packed,大小为自动对齐时的大小,编译时会告警
      printf("struct str_p_t1   size:%ld\n", sizeof(str_p_t1));
      printf("struct str_p_t2   size:%ld\n", sizeof(str_p_t2));
      
      // 输出结果:  
      //    struct str_p_t1   size:29
      //    struct str_p_t2   size:32
      
      

其他编译器支持

  • 和其他 GCC 特性一样,编译器 LLVM Clang 支持了 __attribute__, 还加入了一小部分扩展特性。要检查能否使用特定的属性,可以用 __has_attribute 这个指令。
    https://zhuanlan.zhihu.com/p/140462815  
    关于 LLVM 和 Clang
    LLVM只是一个编译器框架,所以还需要一个前端来支撑整个系统,所以 Apple 又拨款拨人一起研发了Clang,作为整个编译器的前端,Clang用来编译C、C++和Objective-C。
    最初时,LLVM的前端是GCC,后来 Apple 还是立志自己开发了一套 Clang 出来把 GCC 取代了,不过现在带有 Dragon Egg 的 GCC 还是可以生成 LLVM IR,也同样可以取代 Clang 的功能,
    我们也可以开发自己的前端,和 LLVM 后端配合起来,实现我们自定义的编程语言的编译器。
    
posted @ 2021-05-11 18:12  Brucegot9stars  阅读(302)  评论(0编辑  收藏  举报