1 static变量的作用
static修饰变量的作用,如下实例,函数fun()中的count值并不会受到main()中的count值的影响。等于说fun()函数中的count变量对函数外部是不可见的,而且其值会继承上一次的返回值。静态全局变量存储在静态存储区,所以它具备持久性和默认值01。
//实例
#include <stdio.h>
int fun(void){
static int count = 10; // 事实上此赋值语句从来没有执行过
return count--;
}
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
/*
程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
补充:static类型的函数,作用域并不在整个源程序,而仅仅作用于该函数所定义的那个源文件中。
2 结构体变量的初始化
结构体变量中的成员变量前带点,是对成员变量的强制初始化。如下:
//实例:
#include <stdio.h>
struct student{
int year;
int ID;
};
int main(void)
{
struct student s3 ={
.year = 2019,
.ID = 3333};
printf("s3.ID = %d \n",s3.ID);
printf("s3.year = %d \n\n",s3.year);
}
/*
运行结果:
s3.ID = 3333
s3.year = 2019
3 return; 这是啥意思
return;//函数中return后面直接加分号,表示直接返回(void),结束函数。
4 指针中常见的&和*
&取址符号(取旧地址的动作),p指针变量(指向一个新地址),*p指针变量p所指的地址中的内容,实例:
int i = 3;
int *p;
p = &i;
*p = 3
5 C语言符号运算顺序
if (type && strlen(type) >= NAME_LENGTH)
return ERR_PTR(-EINVAL);
这里注意,符号的运算顺序,&&优先级低于>=。所以这里先比较strlen(type)和NAME_LENGTH的大小,然后和type与。
附:各种运算符的优先级:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
6 continue的作用?
continue语句的作用是跳过循环体中剩余的语句而强制进入下一次循环。continue语句只用在 while、for 循环中,常与 if 条件语句一起使用,判断条件是否成立。
#include <stdio.h>
//下面这段代码的功能:输入一段数字,输出时跳过4 5,可以看到continue存在的意义。
int main(){
char c = 0;
while(c!='\n'){ //回车键结束循环
c=getchar();
if(c=='4' || c=='5'){ //按下的是数字键4或5
continue; //跳过当次循环,进入下次循环
}
putchar(c);
}
return 0;
}
运行结果:
0123456789↙
01236789
7 linux宏定义中的(~0)表示什么?
表示对0逐位求反,全部变成1,1的位数由机器决定。
举例:(~0)<<5 如果0是一个u8型的变量,则得值0b11100000
//linux常见宏定义中的(~0)到底指什么?
#include <stdio.h>
int main()
{
printf("%x", (~0));
printf("%d", (~0));
}
/*
运行结果:
ffffffff
-1
8 UL和0UL
1UL //无符号长整型1
0UL //无符号长整型0
9 snprintf()
格式化字符串,并返回字符串长度。
int snprintf ( char * str, size_t size, const char * format, ... );
入口参数:
str – 目标字符串。
size – 拷贝字节数(Bytes)。
format – 格式化成字符串。
… – 可变参数。
返回值:字符串长度
举例:
#include <stdio.h>
int main()
{
char buffer[50];
char* s = "testcase";
// 读取字符串并存储在 buffer 中
int j = snprintf(buffer, 5, "%s\n", s);
// 输出 buffer及字符数
printf("string:\n%s\ncharacter count = %d\n", buffer, j);
return 0;
}
运行结果:
string:
test
character count = 9
这里的size为什么设为5输出的字符串不是“testc”?因为size为5的五个字节包含"test\0"默认以"\0"结尾。
注意返回的是整个被格式化的字符串的长度。
10 "?:"运算符注意事项
最常见的用法如下:
A ? A : B //A为真,返回值为A;A为假,返回值为B
A ?: B //同上,缩写
我们常常用这个运算符来代替if else语句来简化代码。
注意:A和B可以是变量也可以是运算公式或逻辑对比关系,比如:
#include <stdio.h>
int main()
{
int a=7, b=6;
int c;
c = a > b ?: b;//相当于(a>b) ? (a>b) : b
printf("a = %d, b = %d, c = %d\n", a, b, c);
return 0;
}
//运行结果
a = 7, b = 6, c = 1
11 #ifdef和#if defined的区别
两者含义相同,都可以用#endif来结束一个条件的条件编译,区别在于:#if defined可以有两个以上的条件,#ifdef只能有“真”和“非真”两个条件,具体用法如下所示:
#ifdef CONDITION
//......
#else
//......
#endif
#if defined CONDITION_A
//......
#elif defined CONDITION_B
//......
#else
//......
#endif
12 #include <xxx.h>和"xxx.h"的区别
#include <xxx.h> 编译时调用标准头文件(即标准库中的头文件)
#include"xxx.h" 编译时调用自定义头文件,当然,也可以调用标准头文件。但是为了区分出自定义头文件,还是各用各的比较好。
13 头文件中的#ifndef #define #endif?
我们常常会看到头文件以下面的格式编写:
#ifndef _XXX_H
#define _XXX_H
//.....
//头文件中的内容
//....
#endif
那么,在头文件中加这三句的意义是什么呢?
理解:如果没有定义_XXX_H(#ifndef),那么定义_XXX_H(#define),最后结束(#endif)。
在源文件调头文件的时候首先检查这个头文件是否被定义过,这样可以 避免头文件的重定义,防止重复定义宏和重复包含头文件,提高编译效率。
14 数组指针和指针数组
数组指针和指针数组是容易混淆的两个概念,这里先简单介绍下区别,有时间再补充。
指针数组,它肯定是个数组,下面是一个常见的指针数组。这个数组有三个元素,每个元素都是一个char *类型的指针变量(这个指针指向字符串的首地址)
char *parr[3] = {"lost", "in", "paradise"};
数组指针,首先它是一个指针变量,注意是一个。这个指针指向一个数组。比如下面的这个数组指针可以理解为(*arrp)----指向—>char[3],即指向一个有三个char类型元素的数组
char (*arrp)[3];
15 共用体union
union和struct有点类似,都是一种数据结构类型,它们都可以有不同的成员变量包含在其中。但它们的内存大小分配是不同的,union变量的内存大小仅满足所需存储空间最大的成员变量,即存储空间所有成员共用,等于说占用的是同一段内存;而struct变量分配足够的空间给所有成员,每个成员都可以存下,各个成员互不影响。
1.union变量的两种定义方法,和struct类似:
(1)直接定义union类型的变量(推荐)
/* union数据结构在kernel中的应用示例(kernel5.4) */
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
(2)先定义union类型,再定义union类型的变量
/* 类型定义 */
union class
{
char name[10];
int num;
};
/* 变量定义 */
union class class1, class2, *class3;
2.简单举例
#include <stdio.h>
#include <string.h>
union class {
int num; /* int类型占用4个字节 */
char name[20]; /* char类型1个字节,name数组共占用10个字节 */
} cl;
int main() {
cl.num = 30;
/* 当cl.name被赋值时,cl.num所占的内存将被cl.name占用 */
strcpy(cl.name,"math_class");
printf("班级人数 = %d, %p\n", cl.num, &cl.num); /* 打印变量值和存储变量内存的起始地址 */
printf("班级名称 = %s, %p\n", cl.name, &cl.name); /* 起始地址同上 */
cl.num = 30;/* 内存重新覆盖新值 */
printf("班级人数 = %d\n", cl.num);
strcpy(cl.name,"music_class");
printf("班级名称 = %s", cl.name);
return 0;
}
运行结果:
班级人数 = 1752457581, 0x404040
班级名称 = math_class, 0x404040
班级人数 = 30
班级名称 = music_class
3.特别注意union成员的对齐,下面的示例可以看出其对齐规则
#include <stdio.h>
union class {
long num;
char name[20];
} cl;
union class_2 {
int num;
char name[11];
} cl_2;
int main() {
printf("cl.num = %ld \n", sizeof(cl.num));
printf("cl.name = %ld \n", sizeof(cl.name));
printf("union_cl = %ld \n", sizeof(cl)); /* 按long int的8个字节的整数倍对齐 */
printf("union_cl_2 = %ld \n", sizeof(cl_2)); /* 按int的4个字节的整数倍对齐 */
return 0;
}
运行结果:
cl.num = 8
cl.name = 20
union_cl = 24
union_cl_2 = 12
4.共用体有什么实际的意义?
本质上是在内存划了一个可重复利用的空间,内存使用更为精细灵活,也节省了内存空间。
实际应用,比如用于大小端的判断:
#include<stdio.h>
union var{
char c[4];
int i;
};
int main(){
union var data;
data.c[0] = 0x04;
data.c[1] = 0x03;
data.c[2] = 0x02;
data.c[3] = 0x11;
printf("%x\n",data.i);/* 如果i输出的值高位存的是标号小的,则为大端,如果低位存的是标号小的,则为小端 */
}
运行结果:
11020304
说明是小端
16 do{…}while(0)的作用?
内核示例:
// kernel5.4/include/linux/spinlock.h
#define spin_lock_nest_lock(lock, nest_lock) \
do { \
raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \
} while (0)
内核代码中经常能看到do{}while(0),今天在spin_lock_irqsave()的函数宏定义中看到了这个do{…}while(0),就想了解下这个看起来没有意义的while(0)到底起什么作用?
作用一、避免引用时出错
因为在引用宏定义的时候,是单纯字符串的替换,对于行数比较多的复杂宏定义,特别是函数宏定义在引用时很容易出错。如下情况:
#define FUN()\
fun0();\
fun1()
如果调用时只调用FUN();
,那么正常地替换成fun0();\ fun1();
;如果在执行FUN()
前有约束条件,如下所示,会失去原本的语义:
if (a)
FUN();
||
||
v
if (a)
fun0();
fun1();
如果函数宏定义中没有do while(0),我想养成给条件语句的执行语句加括号的习惯,也可以避免这种引用失误。即if(a) { FUN(); }
作用二、代替goto语句
如下例:
do
{
if (条件)
{
执行语句1;
break;
}
执行语句2;
执行语句3;
} while(0)
作用三、避免空宏引起的warning
作用四、定义一个单独的函数块来实现复杂的操作
详细参考链接:
http://www.spongeliu.com/415.html
https://www.cnblogs.com/lanxuezaipiao/p/3535626.html
17 代码段中的goto标签没有被执行,最后还会被执行吗?
疑问引出点,为如下代码段加锁tz->lock,如果if语句执行了,那么goto exit,直接释放锁跳出函数。
如果if语句不执行呢?不执行就不能goto exit,是不是就意味着锁不会被释放了?
因此,代码段中的goto的标签没有被执行,最后还会被执行吗?
//kernel5.4/drivers/thermal/thermal_helpers.c
void thermal_zone_set_trips(struct thermal_zone_device *tz)
{
//.....
mutex_lock(&tz->lock);
if (!tz->ops->set_trips || !tz->ops->get_trip_hyst)
goto exit;
//........
ret = tz->ops->set_trips(tz, low, high);
if (ret)
dev_err(&tz->device, "Failed to set trips: %d\n", ret);
exit:
mutex_unlock(&tz->lock);
}
答案是无论如何都会被执行,除非在goto之前直接return了。简单举例:
#include <stdio.h>
int main()
{
int a = 1;
if(a)
goto enter;
enter:
printf("a=%d\n", a);
exit:
printf("exit!\n");
}
运行结果:
a=1
exit!
exit标签并没有被goto语句调到,但最后还是被执行了!