C语言相关知识

太多的缺省可见性

定义C函数时,在缺省情况下函数的名字是全局可见的。可以在函数的名字前加个冗余的extern关键字。这个函数对于链接到他所在的目标文件的任何东西都是可见的。
如果想限制对这个函数的访问,就必须加个static关键字。

function apple() //在任何地方均可见
extern function pear() //在任何地方均可见
static function turnip() //在这个文件之外不可见

优先级问题

  1. .的优先级高于*
    *p.f表示的是对p取f偏移,作为指针,然后进行解除引用操作:*(p.f)
    ->操作符用于取消掉这个问题
  2. []高于*
    int *ap[]表示的是ap是个元素为int指针的数组:int *(a[])
  3. 函数()高于*
    int *fp()表示的是fp是个函数,返回int*
  4. ==和!=高于位操作符
    (val & mask != 0)表示的是val & (mask != 0)
  5. ==和!=高于赋值符
    c = getchar() != EOF表示的意思是c = (getchar() != EOF)
  6. 算术运算高于移位运算符
    msb << 4 + lsb表示msb << (4 + lsb)

typedef int x[10]和#define x int[10]的区别

  1. typedef是一种彻底的“封装”类型,而#define则是宏文本替换,就仅仅只是文本替换罢了。
  2. 可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
#define peach int
unsigned peach i; //这样是可以的

typedef int banana;
unsigned banana i; //这样是不行的
  1. 在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。如下所示:
#define int_ptr int *
int_ptr chalk, cheese;

经过宏扩展,第二行变为:

int * chalk, cheese;

这使得chalk和cheese成为不同的类型,chalk是一个指向int的指针,而cheese则是一个int。相反,下面的代码中:

typedef char * char_ptr;
char_ptr Bentley, Rolls_Royce;

Bentley和Rolls_Royce的类型相同,都是指向char的指针。

使字符串的比较看上去更自然

因为strcmp方法比较的时候,相等的话是返回0,比较反人类,可以参考下面宏定义的方式来优化:

#define STRCMP(a, R, b) (strcmp(a, b) R 0)

if(STRCMP(s, ==, "volatile"))

数组和指针的区别

  1. 定义指针的时候,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化,如下所示:
    char *p = "bread";
  2. 初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串的值,程序就会出现未定义的行为。与指针相反,由字符串常量初始化的数组是可以修改的。
char a[] = "gooseberry";
strncpy(a, "black", 5);

数据段和堆(heap)

堆区域用于动态分配的存储,也就是通过malloc(内存分配)函数获得的内存,并通过指针访问。堆中的所有东西都是匿名的——不能按名字直接访问,只能通过指针间接访问。
从堆中获取内存的唯一办法就是通过调用malloc(以及同类的calloc、realloc等)库函数。
malloc和free,从堆中获得内存以及把内存返回给堆。
brk和sbrk,调整数据段的大小至一个绝对值。
calloc和malloc类似,但它在返回指针之前先把分配好的内存的内容都清空为零。calloc的意思是“分配清零后的内存”
realloc函数改变一个指针所指向的内存块的大小,既可以将其扩大,也可以把它缩小,它经常把内存拷贝到别的地方然后将指向新地址的指针返回给你。这在动态增长表的大小时很有用。

避免内存泄漏

每次当调用malloc分配内存时,注意在以后要调用相应的free来释放它。
一种简单的方法就是在可能的时候使用alloca()来分配动态内存。当离开调用alloca的函数时,它所分配的内存会被自动释放。

什么时候数组和指针相同

  1. extern,如extern char a[];,不能改写成指针的形式。
  2. 定义,如char a[10];,不能改写成指针的形式。
  3. 函数的参数,如func(char a[]);,你可以随自己喜欢,选择数组形式或者是指针形式。
  4. c = a[i];,你可以随自己喜欢,选择数组形式或者是指针形式。

数组和指针的规则

  1. “表达式中的数组名”就是指针
//假如我们声明:
int a[10], *p, i = 2;
//就可以通过以下任何一种方式来访问a[i]
p = a;
p[i];

p = a;
*(p + i);

p = a + i;
*p;

事实上,可以采用的方法更多。对数组的引用如a[i]在编译时总是被编译器改写成*(a + i)的形式。
编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是4个字节,那么a[i+1]a[i]在内存中的距离就是4。
2. C语言把数组下标作为指针的偏移量

  1. “作为函数参数的数组名”等同于指针
    在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。
    编译器只向函数传递数组的地址,而不是整个数组的拷贝。
    很有意思的是,没有办法把数组本身传递给一个函数,因为它总是被自动转换为指向数组的指针。
    在函数内部使用指针,所能进行的对数组的操作几乎跟传递原本的数组没有差别,只不过,如果想用sizeof(实参)来获取数组的长度,得到的结果是不正确的。
    注意,有一种操作只能在指针里进行,而无法在数组中进行,那就是修改它的值。
    数组名是不可修改的左值,他的值是不能改变的。

状态机

四要素:现态,动作,条件,次态

  • 现态:状态机当前所处的状态
  • 动作:当前所处状态所要执行的动作
  • 条件:当前状态要转成下一个状态所要满足的条件
  • 次态:当前状态的下一个状态
    状态机在当前的状态里面,重复着当前的动作,并且不断检测跳转状态的条件,如果检测到满足条件,就会跳转到下一个状态

volatile

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。所以遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义,例如:假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000:

int *output = (unsigned int *)0xff800000; //定义一个IO端口
int  init(void)
{
	int i;

	for(i=0;i< 10;i++){
		*output = i;
	}
}

经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器最后给你编译后的代码结果相当于:

int init(void)
{
	*output =9;
}

如果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值,显然优化过程并不能达到目的。反之如果你不是对此端口反复写操作,而是反复读操作,其结果是一样的,编译器在优化后,也许你的代码对此地址的读操作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知编译器这个变量是一个不稳定的,在遇到此变量时候不要优化。