通用字符名
C标准支持可用于标识符、字符常量和文本字符串的通用字符名,用以表示基本字符集中未能覆盖的字符。通用字符名Unnnnnnnn指定8位标识符,unnnn指定4位短标识符。
int u4f60u597d = 100; // 这里的标识符相当于:你好
int U0000597d = 50; // 这里的标识符相当于:好
int U0001f60aU0001f601 = U00004f60u597d + U0000597d;
printf("u4f60u597dU0001f60aU0001f601 value is: %d
", U0001f60aU0001f601);
define # #@ ##
"#"用来把参数转换成字符串,是给参数加上双引号。
"##"则用来连接前后两个参数,把它们变成一个参数,
"#@"是给参数加上单引号。
#define CONN(x,y) x##y /* CONN(1,"abc") => "1abc" */
#define TOCHAR(a) #@a /* TOCHAR(1) => '1' */
#define TOSTRING(x) #x /* TOSTRING(123) => "123" */
define _Generic
C11标准中增加了GenericSelection,使得C11支持轻量级的泛型编程,使得可以把一组具有不同类型而却有相同功能的函数抽象为一个接口。
static inline int scabs(signed char v){
return v < 0 > -v:v;
}
static inline int iabs(int v){
return v < 0?-v:v;
}
static inline int fabs
#define ABS(v) _Generic(v, signed char :scabs,
int : iabs,
default:abs)(v)
assert只有在NDEBUG没有定义时起作用
gcc -std=c11 test.c -DNDEBUG -> a:1
gcc -std=c11 test.c ->a:2
所以不要在assert中增加附加操作
#include
#include
int main(void){
int a = 1;
assert(++a);
printf("a:%d
",a);
}
const vs static 函数内、函数外
https://www.cnblogs.com/candyming/archive/2011/11/25/2262826.html
static:
详细说明:
1>、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2>、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
3>当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern,限制在了当前文件里,但是没有改变其存放位置,还是在全局静态储存区。
使用注意:
1>若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
2>若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
3>设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(只要输入数据相同就应产生相同的输出)。
const:
被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。它可以修饰函数的参数、返回值,甚至函数的定义体。
作用:
1>修饰输入参数
a.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a)改为void Func(const A &a)。
b.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int &x)。
2>用const修饰函数的返回值
a.如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
如对于: const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();//cannot convert from 'const char *' to 'char *';
正确的用法是:
const char *str = GetString();
b.如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。 如不要把函数int GetInt(void)写成const int GetInt(void)。
3>const成员函数的声明中,const关键字只能放在函数声明的尾部,表示该类成员不修改对象.
static_assert
编译期间的断言,即静态断言
offsetof(struct test, c)
#define offsetof(s, m) (size_t)&(((s *)0)->m)
1、功能:返回结构体元素的相对结构体首地址的偏移
2、参数:TYPE是结构体类型,MEMBER是结构体中一个元素的元素名
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({
const typeof(((type *)0)->member) * __mptr = (ptr);
(type *)((char *)__mptr - offsetof(type, member));})
1、功能:返回整个结构体变量的指针
2、参数:ptr是指向结构体中一个元素的指针;type是结构体类型;member是结构体中一个元素的元素名
__attribute__((__packed__))
编译器本身可能会往结构体中插入填充数据,以确保每个字段的对齐可以在目标处理器上取得好的性能。
如果正在定义一个和设备要求的结构体相匹配的结构体,这种自动填充会破坏你的意图。解决的办法是告诉编译器结构体必须是“填满的”,不能添加填充符。
就是在结构体声明的地方加上__attribute__((packed))
位域存储
1、一个位域必须存储在同一个字节中,不能跨两个字节;当一个字节所剩空间不够存放下一个位域时,应该从下一个存储单元的起始地址处开始存放该位域;也可以有意使某位域从下一个存储单元的起始地址处开始存放;
例如:
(1)struct BitField
{
unsigned int a:4; //占用4个二进制位;
unsigned int :0; //空位域,占满后面的整个int!;
unsigned int b:4; //占用4个二进制位,从下一个存储单元开始存放;
unsigned int c:4; //占用4个二进制位;
};
此时sizeof(struct BitField)是8!
(2)
struct BitField
{
unsigned int a:4; //占用4个二进制位;
unsigned int :2; //空位域,占两位;
unsigned int b:4; //占用4个二进制位,从下一个存储单元开始存放;
unsigned int c:4; //占用4个二进制位;
};
此时sizeof(struct BitField)是4!
2、由于一个位域不允许横跨两个字节,因此,一个位域的长度不能超过一个字节的长度,也就是说,不能超过8个二进制位;
3、一个位域可以是无名位域,这时这个位域只能用作填充或调整位置;无名位域是不能使用的;例如:
struct BitField
{
unsigned int a:1;
unsigned int :2; //无名位域,不能使用,只能用作填充或调整位置;
unsigned int b:3;
unsigned int c:2;
};
从以上分析可知,位域在本质上仍然是一种结构体,只是其成员是按照二进制位分配的;
volatile
表明某个变量的值可能在外部被改变,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。它可以适用于基础类型如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员都会被视为volatile.
该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。
restrict限定符
https://blog.csdn.net/libing403/article/details/72951538
restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能用于指针,表明该指针是访问该对象唯一且初始的方式。
要弄明白为什么这样做有用,先看几个例子。考虑下面的代码:
int ar[10];
int * restrict restar= (int *) malloc(10 * sizeof(int));
int * par= ar;
这里,指针restar是访问由malloc()所分配的内存的唯一且初始的方式。因此,可以用restrict关键字限定它。而指针par既不是访问ar数组中数据的初始方式,也不是唯一方式。所以不用把它设置成restrict。
现在考虑下面稍微复杂的例子,其中n是int类型:
for (n=0; n<10; n++)
{
par[n]+=5;
restar[n] +=5;
ar[n] *=2;
par[n] +=3;
restar[n] +=3;
}
由于之前声明了restar是访问它所指向的数据块的唯一且初始的方式,编译器可以把涉及restar的两条语句替换成下面的语句,效果相同:
restar[n] +=8;/*可以进行替换*/
但是,如果把与par相关的两条语句替换成下面的语句,将导致计算错误:
par[n] +=8;/*将给出错误的结果*/
_Alignof
alignof(T)返回T的对齐方式,aligned_alloc()以指定字节和对齐方式分配内存
popcount()
/*Return the number of set bits*/
size_t popconut(uintmax_t num){
size_t precision = 0;
while (num != 0){
if (num%2 == 1){
precision++;
}
num >> = 1;
}
return precision;
}