声明函数的属性
Posted WFUF
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了声明函数的属性相关的知识,希望对你有一定的参考价值。
在GNU C中,你可以声明关于在你程序中调用的函数的某些东西,来帮助编译器优化函数调用和更仔细地检查你的代码。
关键字__attribute__
允许你在声明时指定特殊的属性。跟在这个关键字后面的是双重圆括号里面的属性说明。有十四个属性noreturn, pure, const, format, format_arg, no_instrument_function, section, constructor, destructor, unused, weak, malloc, alias and no_check_memory_usage
是目前为函数定义的。在特别的目标系统上,也给函数定义了一些其它属性。其它属性,包括section
都为变量声明(参考 指定变量属性)和类型(参考 指定类型属性)所支持。
你也可以把“__
”放在每个关键字的前面和后面来指定属性。这允许你在头文件中使用它们,而不用关心一个可能有相同名字的宏。比如,你可以使用__noreturn__
而不是noreturn
。
参见5.27节 属性语法来了解使用属性的精确语法细节。
noreturn
一些标准库函数,就像abort
和exit
,不能返回。GCC会自动了解到这一点。一些程序定义它们自己的从不返回的函数。你可以把它们声明为noreturn
来告诉编译器这个事实。比如,
void fatal () __attribute__ ((noreturn)); |
关键字noreturn
告诉编译器去假设fatal
不能返回。那它就能做优化,而不用理会如果fatal
返回会发生什么。这会产生稍微好一点儿的代码。更重要的是,它有助于避免未初始化变量的伪造警告。
不要假设调用函数保存的寄存器在调用noreturn
函数之前被恢复。
对于一个noreturn
函数,有一个除void
之外的返回类型是毫无意义的。
在早于2.5版的GCC中没有实现noreturn
属性。声明不返回值的函数的一个可替代的方法,在当前版本和一些旧的版本中都可以工作,如下:
typedef void voidfn (); |
pure
很多函数除了返回值外没有作用,而且它们的返回值只取决于参数和/或全局变量。这样的一个函数可能依附于普通的子表达式的消除和循环的优化,就像一个算术操作符那样。这些函数应该用属性pure
来声明。例如,
int square (int) __attribute__ ((pure)); |
说明假定的函数square
可以安全地比程序中说的少调用几次。
pure函数的一些常见例子是strlen
和memcmp
。有趣的非pure
函数是带无限循环,或者那些取决于易失性内存或其它系统资源的函数,它们可能在两次连续的调用中间改变(比如在多线程环境中的feof
)。
pure
属性在GCC早于2.96的版本中没有实现。
const
很多函数不检查除它们的参数外的任何值,而且除返回值外没有任何作用。基本上,这比上面的pure
属性稍微更严格一些,既然函数不允许去读全局内存。
注意,带指针参数,而且检查所指向数据的函数不能声明为const
。同样的,调用非const
函数的函数通常也不能是const
。一个const
函数返回void
是没任何意义的。
属性const
在GCC早于2.5的版本中没有实现。声明一个函数没有副作用的一个可替代的方式,能够在当前版本和一些旧的版本中工作,如下:
typedef int intfn (); |
这种方法在2.6.0以后的GNU C++不起作用,既然语言指明const
必须依附于返回值。
format (archetype, string-index, first-to-check)
format
属性指明一个函数使用printf,scanf,strftime
或strfmon
风格的参数,应该通过格式化字符串进行类型检查。比如,声明:
extern int |
会促使编译器检查调用my_printf
中的参数和printf
风格的格式化字符串参数my_format
是否一致。
参数archetype
决定格式化字符串是怎么被解释的,而且应当是printf,scanf,strftime
或strfmon
。(你也可以使用__printf__,__scanf__,__strftime__
或者__strfmon__
。)参数string-index
指定哪个参数是格式化字符串参数(从1开始),而first-to-check
是通过格式化字符串检查的第一个参数。对于参数不可用来检查的函数(比如vprintf
),指定第三个参数为0。在这种情况下,编译器只检查格式化字符串的一致性。对于strftime
格式,第三个参数需要为0。
在上面的例子中,格式化字符串(my_format
)是my_printf
函数的第二个参数,而且要检查的函数从第三个参数开始,所以format
属性的正确参数是2和3。
format
属性允许你去识别你自己的把格式化字符串作为参数的函数,所以GCC可以检查对这些函数的调用错误。编译器总是(除非使用了“-ffreestanding
”)为标准库函数printf,fprintf,sprintf,scanf,fscanf,sscanf,strftime,vprintf,vfprintf
和vsprintf
检查格式,当请求这种警告时(使用“-Wformat
”),所以没有必要修改头文件stdio.h
。在C99模式下,函数snprintf,vsnprintf,vscanf,vfscanf
和vsscanf
也被检查。参考“控制C方言的选项”一节。。
format_arg (string-index)
format_arg
属性指明一个函数使用printf,scanf,strftime
或strfmon
风格的参数,而且修改它(比如,把它翻译成其它语言),所以结果能够传递给一个printf,scanf,strftime
或strfmon
风格的函数(格式化函数的其余参数和它们在不修改字符串的函数中一样)。例如,声明:
extern char * |
促使编译器检查调用printf,scanf,strftime
或strfmon
类型的函数中的参数,其格式化字符串参数是函数my_dgettext
函数的调用,和格式化字符串参数my_format
是否一致。如果format_arg
属性没有被指定,在对格式化函数的这种中,编译器所能告知的一切是格式化字符串参数不是常量;当使用“-Wformat-nonliteral
”时,这会产生一个警告,但如果没有属性,调用将不会被检查。
参数string-index
指定哪个参数是格式化字符串(从1开始)。
format-arg
属性允许你去识别你自己的修改格式化字符串的函数,那样,GCC可以检查对printf,scanf,strftime
或strfmon
类型函数的调用,它们的操作数是对你自己的一个函数的调用。编译器总是以这种方式对待gettext,dgettext
和 dcgettext
,除了当严格的ISO C支持通过“-ansi
”或者一个合适的“-std
”选项请求时,或者“-ffreestanding
”使用时。参考“控制C方言的选项”一节。
no_instrument_function
如果给定“-finstrument-functions
”,在大多数用户编译的函数的入口和出口会生成对概要分析函数的调用。有这个属性的函数将不会被测量。
section ("section-name")
通常,编译器会把它生成的代码放入text
部分。有时,然而,你需要额外的部分,或者你需要某些特别的函数出现在特别的部分。section
属性指定一个函数放入一个特别的部分。比如,声明:
extern void foobar (void) __attribute__ ((section ("bar"))); |
把函数foobar
放进bar
部分。
一些文件格式不支持任意部分,所以section
属性并不是在所有平台上可用的。如果你需要把一个模块的全部内容映射到一个特别的部分,考虑使用链接器的工具。
constructor
destructor
constructor
属性促使函数在执行main()
之前自动被调用。类似地,destructor
属性促使函数在main()
函数完成或exit()
被调用完之后自动被调用。有这些属性的函数对初始化在程序执行期间间接使用的数据很有用。
这些属性目前没有为Objective C所实现。
unused
这个属性,依附于一个函数,意味着这个函数将可能打算不被使用。GCC将不会为这个函数产生一个警告。GNU C++目前不支持这个属性,因为没有参数的定义在C++中是合法的。
weak
weak
属性促使声明被作为一个弱符号导出,而不是全局符号。这在定义库函数时非常有用,它们能够被用户代码覆盖,虽然它也可以和非函数声明一起使用。弱符号被ELF
目标文件所支持,而且当使用GNU汇编器和链接器时也被a.out
目标文件支持。
malloc
malloc
属性用来告诉编译器一个函数可以被当做malloc
函数那样。编译器假设对malloc
的调用产生一个不能替换成其它东西的指针。
alias ("target")
alias
属性促使这个声明被作为另一个必须被指定的符号的别名导出。例如,
void __f () { /* do something */; } |
声明“f
”是“__f
”的一个弱别名。在C++中,目标的重整名字必须被使用。
并不是所有的目标机器支持这个属性。
no_check_memory_usage
no_check_memory_usage
属性促使GCC忽略内存引用的检查,当它为函数生成代码时。通常如果你指定“-fcheck-memory-usage
”(参考“代码生成转换选项”一节),GCC在大多数内存访问之前生成调用支持的例程,以便允许支持代码记录用法和探测未初始化或未分配存储空间的使用。既然GCC不能恰当处理asm
语句,它们不允许出现在这样的函数中。如果你用这个属性声明了一个函数 ,GCC将会为那个函数生成内存检查代码,允许使用asm
语句,而不必用不同选项去编译那个函数。这允许你编写自己的支持例程如果你愿意,而不会导致无限递归,如果它们用“-fcheck-memory-usage
”编译的话。
regparm (number)
在Intel 386上,regparm
属性促使编译器用寄存器EAX,EDX
,和ECX
,而不是堆栈,来传递最多number
个参数。带可变参数数目的函数将会继续在堆栈上传递它们的参数。
stdcall
在Intel 386上,stdcall
属性促使编译器假定被调用的函数将会弹出用来传递参数的堆栈空间,除非它适用可变数目的参数。
cdecl
在Intel 386上,cdecl
属性促使编译器假定调用函数将会弹出用来传递参数的堆栈空间。这对覆盖“-mrtd
”开关的作用很有帮助。
PowerPC上Windows NT的编译器目前忽略cdecl
属性。
longcall
在RS/6000和PowerPC上,longcall
属性促使编译器总是通过指针来调用函数,所以在距当前位置超过64MB(67108864字节)的函数也能够被调用。
long_call/short_call
这个属性允许指定如果在ARM上调用一个特别的函数。两个属性都覆盖“-mlong-calls
”命令行开关和#pragma long_calls
设置。long_call
属性促使编译器总是通过先装入它的地址到一个寄存器再使用那个寄存器的内容来调用这个函数。short_call
属性总是直接把从调用现场到函数的偏移量放进‘BL’指令中。
dllimport
在运行Windows NT的PowerPC上,dllimport
属性促使编译器通过一个全局指针去调用函数,这个指针指向由Windows NT的dll库安装的函数指针。指针名是通过组合__imp__
和函数名来形成的。
dllexport
在运行Windows NT的PowerPC上,dllexport
属性促使编译器提供一个指向函数指针的全局指针,那样它就能用dllimport
属性调用。指针名是通过组合__imp__
和函数名来形成的。
exception (except-func [, except-arg])
在运行Windows NT的PowerPC上,exception
属性促使编译器修改为声明函数导出的结构化异常表的表项。字符串或标识符except-func
被放在结构化异常表的第三项中。它代表一个函数,当异常发生时被异常处理机制调用。如果它被指定,字符串或标识符except-arg
被放在结构化异常表的第四项中。
function_vector
在H8/300和H8/300H上使用这个选项用表明指定的函数应该通过函数向量来调用。通过函数向量调用函数将会减少代码尺寸,然而,函数向量有受限的大小(在H8/300上最多128项,H8/300H上64项),而且和中断向量共享空间。
为此选项,你必须使用2.7版或以后的GNU binutils中的GAS和GLD才能正确工作。
interrupt
在H8/300,H8/300H和SH上使用这个选项表明指定的函数是一个中断处理程序。当这个属性存在时,编译器将会生成函数入口和出口工序,为适应在中断处理程序中的使用。
注意,H8/300,H8/300H和SH处理器的中断处理程序可以通过interrupt_handler
属性来指定。
注意,在AVR上,中断将会在函数里面被启用。
注意,在ARM上,你可以通过给中断属性添加一个可选的参数指定要处理的中断类型,就像这样:
void f () __attribute__ ((interrupt ("IRQ")));
这个参数的允许值是:IRQ, FIQ, SWI, ABORT
和UNDEF
。
interrupt_handler
在H8/300,H8/300H和SH上使用这个选项表明指定的函数是一个中断处理程序。当这个属性存在时,编译器将会生成函数入口和出口工序,为适应在中断处理程序中的使用。
sp_switch
在SH上使用这个选项表明一个interrupt_handler
函数应该切换到一个可替代的堆栈上。它期待一个字符串参数,用来命名一个存放替代堆栈地址的全局变量。
void *alt_stack; |
trap_exit
在SH上为interrupt_handle
使用此选项来使用trapa
而不是rte
来返回。这个属性期待一个整数参数来指定要使用的陷阱号。
eightbit_data
在H8/300和H8/300H上使用此选项来表明指定的变量应该放到8比特数据区。编译器将会为在8比特数据区上的操作生成更高效的代码。注意8比特数据区的大小限制在256字节。
tiny_data
在H8/300H上使用此选项来表明指定的变量应该放到微数据区。编译器将会为在微数据区中存取数据生成更高效的代码。注意微数据区限制在稍微低于32K字节。
signal
在AVR上使用此选项来表明指定的函数是一个信号处理程序。当这个属性存在时,编译器将会生成函数入口和出口工序,为适应在信号处理程序中的使用。在函数内部,中断将会被屏蔽。
naked
在ARM或AVR移植上使用此选项来表明指定的函数不需要由编译器来生成开场白/收场白工序。由程序员来提供这些工序。
model (model-name)
在M32R/D上使用这个属性来设置对象和函数生成代码的可寻址性。标识符model-name是small,medium或large其中之一,各代表一种编码模型。
small模型对象驻留在内存的低16MB中(所以它们的地址可以用ld24指令来加载),可用bl指令调用。
medium模型对象可能驻留在32位地址空间的任何地方(编译器将会生成seth/add3指令来加载它们的地址),可用bl指令调用。
large模型对象可能驻留在32位地址空间的任何地方(编译器将会生成seth/add3指令来加载它们的地址),而且可能使用bl指令够不到(编译器将会生成慢得多的seth/add3/jl指令序列)。
你可以在一个声明中指定多重属性,通过在双圆括号中用逗号来把它们分割开,或者在一个属性声明后紧跟另一个属性声明。
一些人反对__attribute__
特性,建议使用ISO C的#pragma
来替代。在设计__attribute__
时,有两条原因不适合这么做。
- 不可能从宏中生成
#pragma
命令。 - 没有有效说明同样的
#pragma
在另一个编译器中可能意味着什么。
这两条原因适用于几乎任何提议#pragma
的应用程序。为任何东西使用#pragma
基本上都是一个错误。
ISO C99标准包括_Pragma
,它现在允许从宏中生成pragma
。另外,#pragma
GCC名字空间现在为GCC特定的pragma
使用。然而,人们已经发现使用__attribute__
来实现到相关声明的自然的附件属性很方便,而为构造而使用的#pragma
GCC没有自然地形成语法的一部分。查看“C预处理器”中的“多种预处理命令”一部分。
以上是关于声明函数的属性的主要内容,如果未能解决你的问题,请参考以下文章