1. 字符串的表示形式
gcc支持的一种的字符串的表示形式
"xxx" "xxx" "xxx"
会将这3个字符串连成一个并
且只会在最后的一个字符串末尾添加 '\0',而且还会忽略各个字符串之间的空格符号。
2. attribute
attribute_实际上是gcc专有的一种语法,是用来设置设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)的语法
attribute (parameter)
int a __attribute__ ((xxxxx));
struct info{
.....
} __attribute__ ((xxxxx)) sb;
变量属性
aligned
struct fd{
...
...
}__sttribute__ ((align(4))) fd;
//指定struct fd类的字节对齐方式
packed
typedef struct {
char version;
int16_t sid;
int32_t len;
int64_t time;
}__attribute__ ((packed)) Header;
用packed修饰后,变为1字节对齐,这个常用于与协议有关的网络传输中.
函数属性(Function Attribute)
noreturn
noinline
always_inline
pure
const
nothrow
sentinel
format
format_arg
no_instrument_function
section
constructor
destructor
used
unused
deprecated
weak
malloc
alias
warn_unused_result
nonnull
weak , alias
int cpu_mmc_init(bd_t *bis) __attribute__((weak, alias("__def_mmc_init")));
alias属性:指定cpu_mmc_init是后面 "__def_mmc_init" 函数的一个别名,所以
cpu_mmc_init函数如果没有定义,那么调用cpu_mmc_init其实就是会调用__def_mmc_init函数,如果定义就不会调用这个了,注意__def_mmc_init函数是一定要有定义的。
weak属性:即使cpu_mmc_init函数没有定义,调用cpu_mmc_init编译器也是不会报错的,一般会和alias属性连用。
unused
static void func(void) __attribute__((unused));
指定这个变量或者函数如果没有被使用也不要输出警告信息。
section
指定该函数或者是变量最后链接在我们的指定段中
我们链接脚本中使用的段名其实就是一个变量的形式,他是不需要定义的,能够直接用,也就是说,我们可以在连接脚本中随意使用一个段名
#define __init __attribute__ ((__section__ (".init.text"))) __cold
//驱动模块的初始化函数放入名叫.init.text的输入段
#define __initdata __attribute__ (( __section__ (".init.data")))
//将数据放入名叫.init.data的输入段
#define __exitdata __attribute__ (( __section__ (".exit.data")))
[23] .data PROGBITS 0000000000600900 00000900
0000000000000004 0000000000000000 WA 0 0 4
[24] .init.data PROGBITS 0000000000600904 00000904
0000000000000018 0000000000000000 WA 0 0 4
[25] .bss NOBITS 0000000000600920 0000091c
0000000000000010 0000000000000000 WA 0 0 8
未指定section属性时,如果已初始化,则存入.data段;否则存入.bss段。
如果指定section属性,则无论是否初始化,都存入指定段。未初始化变量不会位于.bss段中。
noreturn
void __attribute__((noreturn)) die(void);
从gcc的描述来看,这个属性主要是用来生成优化的编译代码,对于代码的正确性检查没有什么帮助。
主要的功能用来告诉编译器,调用了attribute(noreturn)的函数后,控制不会再返回caller
noinline always_inline
inline int add(int a, int b) __attribute__((always_inline));
inline int add(int a, int b) __attribute__((noinline));
noinline:强制不内联。
一个函数,如果代码量比较少的话,用 -O3优化开关的话,gcc有可能将这个函数强制内联(inline)即使,你在函数前没有写inline助记符。
noinline 关键字用来通知编译器不要内联这个函数。
定义为inline也不一定统统都会被内联,内不内联最后是由gcc编译器决定
pure
pure表明函数除返回值外,不会通过其它(如全局变量、指针)对函数外部产生任何影响
只需要为相同的参数调用一次,即如果编译器认为适合这样做,那么结果可以被缓存
char *x = calloc(1, 8);
char *y = calloc(1, 8);
if (memcmp(x, y, 8) > 0)
printf("x > y\n");
x[1] = 'a';
if (memcmp(x, y, 8) > 0)
printf("x > y\n");
这样会调用两次,因为x里面的内存变化了。
如果没有这一句,则只会调用一次,第二次会使用缓存。
//假设自定义atoi时,里面有打印
int ret = atoi(ptr);
atoi(ptr);
当不需要它的返回值时,它可以被优化掉,但是我们自己的实现中是有打印输出的,所以造成声明和实现不一致。
const
const函数是更严格的__pure函数
因为带有数值参数的函数返回值是相同的,所以在多次调用的时候,编译器进行优化,只需要执行一次就可以得到执行的结果.
const attribute是让编译器进行优化.
for(int i = 0; i < fibonacci(100); ++i)
{
...
}
只执行一次,相当于
for(int i = 0,end = fibonacci(100); i < end; ++i)
{
...
}
//这是错误的
int output() __attribute__((const));
int output()
{
std::cout << "lol | ";
return 1;
}
pure/const 函数只能读,不能写,不能做print等事情。
constructor/destructor
若函数被设定为constructor属性,则该函数会在 main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行
__attribute__((constructor)) void before_main() {
printf("--- %s\n", __func__);
}
__attribute__((destructor)) void after_main() {
printf("--- %s\n", __func__);
}
format
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
format属性告诉编译器,按照printf, scanf,
strftime或strfmon的参数表格式规则对该函数的参数进行检查
m:第几个参数为格式化字符串(format string);
n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l,const char *format,...)
__attribute__((format(printf,2,3)));
3. 零长度数组
有时候需要在结构体中存放一个长度动态的字符串,比如说,我们要在结构体中存放一个名字,但是这个名字的长度是未知的。于是,我们就会采用以下两种方法来解决这个问题。
#include <stdio.h>
#include <malloc.h>
#include <string.h>
struct node{
int num;
int data;
char name[0]; // 结构体最后的成员定义一个长度为[0]的数组
};
char test_name[] = "testname";
int main(void)
{
struct node *test_node;
int length = strlen(test_name);
test_node = (struct node*)malloc(sizeof(struct node)+length);
//strncpy((char *)test_node+1, test_name, lenght);
strncpy(test_node->name, test_name, lenght);
printf("sizeof(struct node) = %d\n", sizeof(struct node));
printf("num addr:%p\n", &(test_node->num));
printf("data addr:%p\n", &(test_node->data));
printf("name addr:%p\n", test_node->name);
printf("test_node+1 addr:%p\n", test_node+1);
printf("name = %s\n", test_node->name);
printf("name[0] = %c\n", test_node->name[0]);
printf("name[%d] = %c\n", lenght-1, test_node->name[lenght-1]);
free(test_node);
}
sizeof(struct node) = 8
num addr:0x1574010
data addr:0x1574014
name addr:0x1574018
test_node+1 addr:0x1574018
name = testname
name[0] = t
name[7] = e
例化test_node只调用一次malloc(),意味着最后也只要free()一次。为其申请了一个长度为sizeof(struct node)+lenght的空间,所申请到的空间是连续的,没有小内存的情况出现。减小了对内存管理性能的影响。
4. 语句表达式
GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环变量、局部变量等
#define min_t(type,x,y) \
({ type _ _x = (x); type _ _y = (y); _ _x < _ _y ? _ _x: _ _y; })
int ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa, fb);
因为重新定义了_ xx 和 _y 这两个局部变量,所以上述方式定义的宏将不会有副作用。
在标准 C 中,对应的如下宏则会产生副作用:
define min(x,y) ((x) < (y) ? (x) : (y))
代码 min(++ia,++ib)会被展开为((++ia) < (++ib) ? (++ia): (++ib)),传入宏的参数被增加两次。
5. 类型发现 typeof 关键字
typeof(x)语句可以获得 x 的类型,因此,我们可以借助 typeof 重新定义 min 这个宏:
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
我们不需要像min_t(type,x,y)这个宏那样把type传入,因为通过typeof(x)、typeof(y)可以获得 type。
(void) (&_x == &_y)
的作用是检查_x 和_y 的类型是否一致。
2个不一样的指针类型进行比较操作,则会引起编译器产生一个编译警告,提示你这两个值的类型不同。
6. 范围扩展
static int sd_major(int major_idx)
{
switch (major_idx) {
case 0:
return SCSI_DISK0_MAJOR;
case 1 ... 7:
return SCSI_DISK1_MAJOR + major_idx - 1;
case 8 ... 15:
return SCSI_DISK8_MAJOR + major_idx - 8;
default:
BUG();
return 0; /* shut up gcc */
}
}
/* Vector of locks used for various atomic operations */
spinlock_t cris_atomic_locks[] = { [0 ... LOCK_COUNT - 1] = SPIN_LOCK_UNLOCKED};
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
7. 内建函数
判断调用地址
内建函数_ _builtin_return_address (LEVEL)返回当前函数或其调用者的返回地址,参数 LEVEL 指定调用栈的级数,如 0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址。
常量检测
内建函数_ _builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数 EXP 的值是常数,函数返回 1,否则返回 0。
#define roundup_pow_of_two(n) \
( \
__builtin_constant_p(n) ? ( \
(n == 1) ? 1 : \
(1UL << (ilog2((n) - 1) + 1)) \
) : \
__roundup_pow_of_two(n) \
)
分支预测提示
内建函数_ _builtin_expect(EXP, C)用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时常数。
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
如果一个条件标上了 “likely”,那么编译器可以把代码的 True 部分直接放在分支指令后面(这样就不需要执行分支指令)。
if (likely(a>b)) {
fun1();
}
//这样就cache在预取数据时就可以将fun1()函数的二进制代码拿到cache中。这样,也就添加了cache的命中率。
网友评论