Linux C语言面试考点

数组

数组初始化方法

/* 以下为自动类型 */

/* 一维数组 */
int arr[] = {1, 3, 5}; //不指定长度,由编译器自动计算
int arr[5] = {0, }; //指定长度,按顺序初始化元素,未初始化的会自动初始化为0

int arr[] = {1, 3, [5]=2, [0]=3, 6, };
//可以通过指定下标初始化,不连续的部分自动初始化为0,先前元素的值以最后一个初始化结果为准
//以上数组成员等同于{3, 3, 0, 0, 0, 2, 6},编译器会把数组大小设置成能装下所有初始值

/* 二维数组 */
int arr[2][2] = {1,3,[1][1]=5,}; //和一维数组类似
int arr[][2] = {{1,3}, {2, 4}}; //必须指定列数

和指针的关系

#include <stdio.h>

int main(int argc, char const *argv[])
{
   int arr[2][2] = {1,3,[1][1]=5,};
   int *p = arr[0];
   int size = sizeof(arr)/sizeof(int);
   for(int i=0; i<2; i++) {
       for(int k=0; k<2; k++)
           printf("ptr = %p       val = %d\n",(*(arr+i))+k, *(*(arr+i)+k));
  }
   printf("%d,",arr[0][0]);
   printf("%d,",arr[0][1]);
   printf("%d,",arr[1][0]);
   printf("%d\n",arr[1][1]);
   printf("sizeof p = %d, p=%p\n", sizeof(p), p);
   printf("sizeof arr = %d, sizeof int = %d\n", sizeof(arr), sizeof(int));
   printf("arr = %p, arr+1 = %p arr[0][0] = %d\n", arr, arr+1, *p);

   return 0;
}
pi@WIN-3MPRRM0J097:/mnt/c/Users/Administrator/Desktop$ ./test
ptr = 0x7fffcec1bc90       val = 1
ptr = 0x7fffcec1bc94       val = 3
ptr = 0x7fffcec1bc98       val = 0
ptr = 0x7fffcec1bc9c       val = 5
1,3,0,5
sizeof p = 8, p=0x7fffcec1bc90
sizeof arr = 16, sizeof int = 4
arr = 0x7fffcec1bc90, arr+1 = 0x7fffcec1bc98 arr[0][0] = 1

指针

在C中,指针+1指的是 【增加一个存储单元 】。对数组而言,指针+1后的地址是下一个元素的地址,而不是下一个字节的地址。所以,这就是为什么必须声名指针所指向对象的类型的原因之一:只知道地址还不够,还有知道指向对象存储多少字节,否则不能正确取回地址上的值。

枚举

enum color{ <---------  枚举类型名(可以不写)
   red, <----- 成员名(是常量,默认从0开始)
   pink = 2,
   blue   <----- 会从3开始
}my_color; <------- 变量名(可以不写并单独定义,可以同时定义多个,只能赋值成员的值,否则无意义)

联合体

union Data          <-------类型名(可以不写)
{   <------Data 将占用 20 个字节的内存空间
  int i;
  float f;
  char  str[20];
}data; <-------变量名

结构体对齐

#pragma pack(8) //ANSI C

__attribute__ ((packed)) //GNU C 取消结构体对齐

类型强制转换

丢失精度:比如int 类型抓换成char类型将会丢失高位数据。

升级:int -> unsigned int -> long -> unsigned long -> long long -> unsigned long long -> float -> double -> long double

注意将int 负数转换成unsigned int 后会变成一个很大的正数。

运算符

优先级:

短路原则:|| (逻辑或)和&& (逻辑与)都有短路原则

浮点数比较

C 中默认浮点常量为double类型,浮点数比较都不能使用== 或者 !=运算符

float的有效值小数点后6位

与零比较 if(num >= -0.000001 && num <= 0.000001)

inline

函数调用都有一定的开销,内联函数会用内联代码替换函数调用。编译器可能会用内联代码替换函数调用,并(或)执行一些其他优化,但是也可能不起作用。由于并未给内联函数预留单独的代码块,所以无法获得内联函数的地址(实际上能够获得地址,但是这样做之后编译器将会生成一个非内联函数)。

内联函数一般较为短小,如果函数执行的时间比调用的时间多太多,也就失去了内联优化的意义。

inline static void eatline(void)
{
   while(getchar() != '\n')
      continue;
}
int main()
{
  ...
       eatline();
  ...
}
/*===================================================*/
//编译器会把内联函数的调用做成以下形式
int mian()
{
  ...
   while(getchar() != '\n')
       continue;
  ...
}

const

const限定符告诉编译器 const修饰的变量是一个只读变量,从而对这个变量起到保护的作用,如定义一个指向只读变量的指针并把这个指针指向一个只读寄存器。

我们不能直接修改这个变量的值,虽然我们可以用一个指针指向这个只读变量并修改它,但是这样做是危险且无意义的。

volatile

volatile 限定符告诉编译器 volatile修饰变量的值可能会被不是程序本身改变。因此,这样程序调用的时候就会每次到相对应的内存地址中读取。

使用volatile的情况有3种:

//1、指向寄存器的指针。因为该指针指向的值在程序运行过程中可能会被硬件置位。
   unsigned int * reg = 0x1234FFFF;
//2、在中断中使用的全局变量。应为该全局变量随时被主程序或者中断程序修改。
//3、在多线程中共同使用的全局变量。应为该全局变量随时会被其他进程修改。

register

存储类别说明符register可以声明寄存器变量。编译器必须根据必须根据寄存器或最快可用内存的数量来衡量你的请求,或者直接忽略你的请求。注意 对于register变量不能使用地址运算符(取地址),因为CPU寄存器不是内存,没办法使用内存的操作方式。

static

存储类别说明符static修饰的变量具有静态存储周期(他们在内存的地址不变,并且在离开他们所在的函数后这些变量不会消失)。static修饰的全局变量和函数具有文件作用域(无连接),这将不能在其他文件中调用这些全局变量和函数。

对于函数,在定义时除非使用static 关键字,否则默认使用extern (全局作用域)。

extern

我们可以使用extern引用声明一个外部变量(全局变量)或函数。注意 外部变量只能初始化一次,且必须在定义该变量的时候。C99和C11只要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符。

宏定义

定义一个标准MIN宏

#define MIN(x, y)   \
({ \
   typeof(x) __tmp0 = (x); \ //防止i++的情况
   typeof(y) __tmp1 = (y); \
  (void) (&__tmp0 == __tmp1); \
       //如果两个变量的类型不一样,那么编译器就会给一个warning,加上void是防止部分编译器在不使用运算结果时报warning
       __tmp0 < __tmp1 ? __tmp0:__tmp1;
})

宏定义的作用:在程序预处理的时候进行文本替换

头文件中:#ifndef #define #endif的作用:防止头文件重复包含

宏定义中##的作用:将两个宏记号组合成一个

#define FUNC_ARG(x) arg ## x
//当x为0时,该宏替换之后为 arg0

宏定义在#的作用:将宏参数视为一个字符串

#define DBUG_PRINT(x)   printf("DEBUG:" #x "\n");
//如果x为hello,该宏替换之后为 printf("DEBUG:" "hello" "\n");

已经内置且不能取消定义的宏:

__FILE__    //当前程序的.c文件名
__TIME__ //编译时间
__DATE__ //预处理的日期
__LINE__ //当前行号
   
__func__ //预定义标识符,不是宏定义。当前函数名(C99)

#line和#error

#line //指令重置__LINE__和__FILE__ 宏报告的行号和文件名
#line 100 //将当前行号设置为100
#line 200 "hello.c" //把当前行号设置为10,把文件名设置为hello.c

#error //指令让预处理器发出一条错误消息,该消息包含指令中的文本。
//如果可能的话,编译过程应该中断。
#ifndef XXX
#error undefined XXX
#endif

//编译器输出结果如:xxx.c:13:2:error: #error undefine XXX

typedef

typedef一个指向函数的指针

typedef int (*pfunc_t)(int *args,unsigned int size);//给函数指针起个别名

pfunc_t func;//定义一个函数指针

int cut(int *args, unsigned int size)
{
   return size;
}

func = cut;

全局变量(外部变量)

未初始化的全局变量会自动初始化为0,这适用于数组和自定义元素(结构体等)。

全局变量只能使用常量进行初始化。

复合字面量

C99允许使用复合字面量。字面是除符号常量外的常量由于复合字面量是匿名的所以不能先创建然后在使用它,必须在创建的同时使用它。复合字面量是提供只临时需要的值的一种手段。

int *p;
p = (int[]){1,2,4,5}; //可以指定数组大小也可不写让编译器自动识别

printf("hello!" "world\n"); //编译器会将两个字符串常量进行拼接

malloc()/calloc()/free()

void * malloc(size_t size);
void free(void *ptr); //不能双重释放一个指针,但是可以释放一个空指针
void *calloc(size_t nmemb, size_t size); //申请到的内存值会初始化为0

存储类别

存储类别存储期作用域链接声名方式
自动自动块内
寄存器自动块内,使用register关键字
静态外部链接静态文件外部所有函数外
静态内部链接静态文件内部所有函数外,使用static关键字
静态无链接静态块内,使用register关键字

存储空间

BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新 分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变 量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以 栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

断言库

assert.h 头文件支持的断言库是一个用于辅助调试程序的小型库。

//#define NDEBUG    //该宏可以关闭assert(),如果需要开启断言可以注释此行
#include <assert.h>
int main()
{
...
      assert(z > 0);
  ...
}
assert()宏接受一个整形表达式作为传参。如果表达式的值为假,assert()宏就在标准错误流(stderr)中写入错误信息,从而打印到屏幕上。并调用abort()函数终止程序。如果assert()终止了程序,它首先会显示失败的测试、包含测试的文件和行号。