short, long
short int x;
long int y;
short 和 long 用于修饰整型,和 int 一起使用,但使用时 int 关键字可以省略。
short, int, long 在不同的机器架构下占用的长度不同,但一般遵循以下限制:short长度小于等于int,int长度小于等于long,一般short长度至少需要16位,long长度至少需要32位。
extern
引用全局变量(外部变量)时需要先使用 extern 关键字来声明对变量的引用,格式:extern dataType dateName; 如果全局变量的声明和引用都在同一个源文件中,那么可以省略使用 extern。
const
任何变量的声明都可以用const修饰,该修饰符表示所修饰的变量的值不能够被修改,如果const修饰的是数组,那么所有数组元素的值都不能被修改。const也可以用来修饰作为函数参数的数组,表明函数不能对入参数组的元素的值进行修改。
const double e = 2.718281;
const char msg[] = "warning:"
int strlen(const char[]);
static
用static修饰的变量或函数,可以将其声明后的对象的作用域限定在被编译的源文件的剩余的部分,以达到隐藏对象的目的。简单说就是:全局对象对于程序全局可见,静态对象只在文件内可见。
另外,static可以在函数内修饰变量。此时该变量只在函数内可见,但变量的生命周期不跟随函数声明周期变化,是一直存在的(会一直占用内存)。
register
用register修饰变量,所要表达的是意思:想要编译器把变量存放在寄存器中,这样可以让函数占用的内存更小,运行的速度更快。不同的硬件环境对register变量的支持不一样,而且编译器可以也忽略register修饰。
指针和数组
- 代码中使用数组名a,即代表数组a的首地址。pa = a 等价于 pa = &a[0]
- 无论数组a中的元素类型和数组长度是什么,对指针加1就意味着,pa+1指向pa所指向的对象的下一个对象(是对象个数的偏移量,而不是物理地址的偏移量)。
- 对数组元素a[i]的引用,也等价于*(a+i)的指针形式。
void *
任何指针都可以转换为void *类型的指针,并且将它转换为原来的类型时不会丢失信息。
复杂指针声明举例
C语言的语法力图使声明和使用相一致,对于简单情况,这种做法是很有效的,但是对于情况比较复杂的情况,则容易让人混淆。
char **argv; // argc是指向char的指针的指针
int (*daytab)[13]; // daytab是指向数组(int[13])的指针
int *daytab[13]; // daytab是一个数组(int *[13]),数组存储指向int的指针
void *comp(); // com是一个函数,返回一个void *类型的指针
void (*comp)(); // com是一个指向函数的指针,该函数返回void类型的对象
上面的几个声明还算好理解,下面这两个就更复杂了。如果想要分析复杂的指针声明,需要用递归下降的算法思想(从反方向),在这些声明中,越靠近name的修饰符优先级越高,越能表达name的本质属性,越靠近外围的修饰符优先级越低,是对name的补充修饰。这里给出分析下面声明的递归定义:
dcl: 前面带有可选的*的direct-dcl
direct-dcl: name
(dcl)
direct-dcl()
direct-dcl[可选长度]
char (*(*x())[])(); // x是一个函数,这个函数返回一个指向数组的指针;数组中的元素是一个指针,这个指针指向一个函数,这个函数返回一个char对象
char (*(*x[3])())[5]; // x是一个数组[3];数组中的元素指向一个函数,该函数返回一个指针;该指针一个数组[5],数组中的元素是char
我们以 char (* (* x())[] )(); 来解析一下:
- x() 声明 x 是一个函数,*x() 的 * 修饰了函数 x 返回了一个指针,但指向何处需要外层继续修饰
- (*x())[],声明了上面返回的指针指向的是一个数组,数组里面存的什么值需要外层继续修饰
- *(*x())[],声明了上面的数组中存的是一个指针,指向何处需要外层继续修饰
- (*(*x())[])(),声明了上面指针指向的是一个函数,函数的返回值需要外层继续修饰
- char (*(*x())[])(),声明了上面的函数的返回值是一个char对象
结构体
结构体是可以嵌套的,但不能嵌套自己(可以通过指针)
struct rect {
struct point pt1;
struct point pt2;
// struct react re1; 非法
struct react *reptr; // 指针合法
}
typedef
typedef在C代码移植的时候可以发挥很大作用,只需要把typedef修饰的类型改为当前平台下的类型即可。
// 举例
typedef struct tnode *Treeptr; // 定义Treeptr是指向结构体tnode的指针
typedef struct tnode { // 定义Treenode是结构体tnode的别名
//...
} Treenode;
typedef int(*PFI)(char *, char*) // 定义PFI是一个指向函数的指针,该函数的入参是两个char指针,返回一个int对象
bitfield
位段利用一个机器字(word)中的不同Bit表示信息,最大化的利用了内存。有些机器字段的分配是从左到右,有些方向相反。
struce {
unsigned int id_keyword : 1;
unsigned int id_extern : 1;
unsigned int id_static : 1;
} flags
内存对齐
为什么要进行内存对齐呢?
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
参考资料:
C++ 内存对齐,失传的C结构体打包技艺
C实战
用C实现内存管理函数malloc()和free()
typedef long Align; // 按照Long对齐边界
// 这里声明了一个union,长度为一个long的长度,其中Align x只为了占住内存长度(内存对齐用),本身无意义。struct s是有意义的数据,标明了当前可用内存块的大小和指向下一块可用内存。
union header {
struct {
union header *ptr; // 之下下一块空闲内存
unsigned size; // 当前空前内存块的大小
} s;
Align x; // header块的对齐,x本身无用
}
typedef union header Header;
static Header base; // 空闲内存的起始地址
static Header *freep = NULL; // 空闲链表
void *malloc(unsigned nbytes) {
Header *p, *prep;
Header *moreCore(unsigned);
unsigned nunits;
nunits = (nbytes + sizeof(Header) - 1)/sizeof(Header) + 1; // 分配的内存是Header的整数倍(内存对齐用),且>nbytes的需求
if ((prep = freep) == NULL) { // 当前没有空闲链表
base.s.ptr = freep = prep = &base;
base.s.size = 0;
}
for (p = prep->s.ptr; ; prep = p, p = p->s.ptr) {
if (p.size >= nunits) { // 内存足够
if (p.size = nunits) { // 内存大小正好
prep.s.ptr = p.s.ptr;// 把这块内存拿去用,指向跨越p
} else { // 从末尾开始分配
p->s.size -= nunits;
p += p->s.size; // p指向
p->s.size = nunits;
}
freep = prep;
return (void *)(p + 1);
}
if (p = freep) { // 空闲链表已闭环
if ((p = morecore(nunits)) == NULL)
return NULL; // 没有剩余空间了
}
}
}
#define NALLOC 1024; // 申请最小单元
// morealloc函数,向系统申请更多内存
static Header morealloc(unsigned nunits nu) {
char *cp, *sbrk(int);
Header *up;
if (nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if (cp = (char *) - 1) { // 没有空间
return NULL;
}
up = (Header *)cp;
up->s.size = nu;
free((void *)(up + 1));
return freep;
}
void free(void *ap) {
Header *bp, *p;
bp = (Header *)ap - 1; // 指向块头
for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
if (p >= p->s.ptr && (bp > p || bp < p->s.ptr))
break; // 被释放的块在链表的开头或结尾
if (bp + bp->s.size == p->s.ptr) { // 与上一个相邻的块合并
bp->s.size += p->s.ptr->s.size;
bp->s.ptr = p->s.ptr->s.ptr;
} else {
bp->s.ptr = p->s.ptr;
}
if (p + p->s.size = bp.) { // 与下一个相邻的块合并
p->s.size += bp->s.size;
p->s.ptr = bp->s.ptr;
} else {
p->s.ptr = bp;
}
freep = p;
}