a 一个整型数 (an integer) //int a
b 一个指向整型数的指针 (a pointer to an integer) //int *a
c 一个指向指针的指针,它指向的指针是指向一个整型数 (a pointer to a pointer to an integer)
int **a
d 一个有10个整型数的数组 (an array of 10 integers)
int a[10]
e 一个有10个指针的数组,该指针指向一个整型数 (an array of 10 pointers to integers)
int *a[10]
f 一个指向有10个整型数数组的指针 (a pointer to an array of 10 integers)
int (*a)[10]
g 一个指向函数的指针,该函数有一个整型参数并返回一个整型数
(a pointer to a function that takes an integer as an argument and returns an integer)
int (*a)(int)
h 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
an array of ten pointers to functions that take an integer argument and return an integer
int (*a[10])(int)
关键字是一种类型修饰符。
volatile
当使用volatile声明变量值的时,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。
1. 用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。
2. 没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值。
(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中)
之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果。
(访问cpu寄存器比访问ram快的多)
register
编译程序相应的变量将被频繁使用,如果可能应将其保存在CPU的寄存器中,加快其存储速度。
const
1 const int a 一个常整型数
2 int const a 同const int a
3 const int *a 一个指向常整型数的指针(整型数不可修改,但指针可以)
4 int * const a 一个指向整型数的常指针(指针指向的整型数可以修改,指针不可以修改)
5 int const *a const 一个指向常整型数的常指针(指针指向的整型数不可修改,指针也不可以修改)
前两个作用一样,欲阻止一个变量被改变可以使用const。
在定义该const变量时通常需要初始化,因为以后就没有机会改变。
exit return
exit 函数,用于结束正在运行的整个程序,它将参数返回给OS,把控制权交给操作系统。
系统调用级别,它表示一个进程的结束。它将删除进程使用的内存空间,同时把错误信息返回父进程。
exit(int n)直接退出程序,默认的标准程序入口为 int main(int argc, char** argv),返回值是int型,
正常返回值为0。
一般在shell下面,运行一个程序,然后使用命令echo $?就能得到该程序的返回值,也就是退出值。
对于单独的进程exit的返回值是返回给操作系统,对于多进程,则是返回给父进程。
父进程里面调用waitpid()等函数得到子进程退出的状态,以便作不同处理。
根据相应的返回值让调用者作出相应的处理。
return 关键字, 退出当前函数,返回函数值,把控制权交给调用函数。
语言级别,表示调用堆栈的返回。
在main函数结束时,会隐式地调用exit函数,所以一般程序执行到main结尾时,则结束主进程。
exit 将删除进程使用的内存空间,同时把错误信息返回给父进程。
语句块用大括号划分,{}之间的内容是一个语句块。每个语句块可以有一个名字(函数名)。
语句块有以下性质:
1.可以没有名字,可以是空的
2.可以包含没有名字的语句块,不能包含有名字的语句块
int main(void) //main语句快开始
{
int c = 3;
printf("c = %d &c = %d\n", c, &c); //变量c的值是3
{ //无名语句块开始
printf("c = %d &c = %d\n", c, &c); //打印变量c的值为3
//调式时编译器会在语句块开始时给c分配地址,
//但赋值在下一行,所以在这里c是无效值
int c = 4; //变量c作用域从定义这一行到此语句块结束
printf("c = %d &c = %d\n", c, &c); //变量c的值是4
} //无名语句块结束
printf("c = %d &c = %d\n", c, &c); //变量c的值是3
return 0;
} //main语句块结束
#运算符 把replace-text转换为用引号引起来的字符串
#define MKSTR(x) #x
MKSTR( Hello World! ) --> "Hello World!"
##运算符 用于连接两个令牌
#define concat(a, b) a ## b
int xy = 10; concat(x, y) --> xy --> 10
#define DEBUG_1(fmt, args...) printf(fmt, ##args)
#define DEBUG_2(fmt, ...) printf(fmt, ##__VA_ARGS__)
DEBUG_1("F: %s, L: %d\n", __FILE__, __LINE__);
DEBUG_1("DEBUG\n"); //宏替换为 printf("DEBUG:\n"); //right
#define DEBUG_1(fmt, args...) printf(fmt, args)
#define DEBUG_2(fmt, ...) printf(fmt, __VA_ARGS__)
DEBUG_1("DEBUG:\n"); //宏替换为 printf("DEBUG:\n", ); //error
#define npp_debug(...) fprintf(stderr, __VA_ARGS__)
#define npp_print(...) fprintf(stdout, __VA_ARGS__)
npp_print(" Set NEQ\n");
计算机系统对基本类型数据在内存中存放的位置有限制,它会要求这些数据的起始地址的值是某个数k的倍数,这就是所谓的内存对齐。而这个k则被称为该数据类型的对齐模数(alignment modulus)。
这种强制要求简化了处理器与内存之间传输系统的设计,可以提升读取数据的速度。
比如一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读取或写入8个字节的数据,如软件能保证double类型的数据都从8倍数地址开始,那么读/写一个double类型数据就只需一次内存操作。否则就有可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。
结构体对齐包括两个方面的含义
1.结构体总长度;
2.结构体内各数据成员的内存对齐,即该数据成员相对结构体的起始位置
结构体大小的计算方法
1.将结构体内所有数据成员的长度值相加记为sum_a
2.将各数据成员为了内存对齐,按各自对齐模数而填充的字节数累加到sum_a上,记为sum_b
对齐模数是#pragma pack指定的数值以及该数据成员自身长度中数值较小者
该数据相对起始位置应该是对齐模式的整数倍;
3.sum_b向结构体模数对齐
该模数是(#pragma pack指定的数值)【未指定#pragma pack时,系统默认的对齐模数
(32位系统为4字节,64位为8字节)】和(结构体内部最大的基本数据类型成员的长度)数值较小者。
结构体的长度应该是该模数的整数倍。
可重入函数主要用于多任务环境中。
一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,
转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误。
不可重入函数 不能运行在多任务环境中。
由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题。
#include
void test(void (*p)(void)) {
//p(); (*p)();
p();
return;
}
void print(void) {
printf("hello\n");
}
int main(void) {
void (*pf)(void);
pf = print;
//pf();
test(pf);
}
int main()
{
int a[5][5];
int (*p)[4];
p = a;//p指向&a[0]
printf("a_ptr=%#p, p_ptr=%#p\n", &a[4][2],&p[4][2]);
printf("%p, %d\n", &p[4][2]-&a[4][2], &p[4][2]-&a[4][2]);
return 0;
}
指针操作
#include
void func(int **x,int **y){
int *p;
p = *x;
*x = *y;
*y = p;
//printf("%d %d",*x,*y);
}
int main()
{
int a=3,b=4,*x=&a,*y=&b;
func(&x,&y);
printf("%d %d %d %d", a, b, *x, *y);
return 0;
}
//3 4 4 3
//
void func(int *x,int *y){
int *p;
p = x;
x = y;
y = p;
//printf("%d %d",*x,*y);
}
int main(){
int a=3,b=4,*x=&a,*y=&b;
func(x,y);
printf("%d %d %d %d", a, b, *x, *y);
return 0;
}
//3 4 3 4
#include
#include
int main(void) {
char *p;
p = getenv("LOGNAME");
if(p == NULL)
printf("not found\n");
else
printf("%s\n", p);
setenv("LOGNAME", "haha", 1);
p = getenv("LOGNAME");
if(p == NULL)
printf("not found\n");
else
printf("%s\n", p);
return 0;
}
open,read, write, seek等,用户空间应用程序和内核提供的服务之间的一个接口。由于服务是在内核中提供的,因此无法执行直接调用,必须使用一个进程来跨越用户空间与内核之间的界限。
系统调用是通过中断向内核发请求,实现内核提供的某些服务。
意义:为用户提供一种硬件的抽象接口,在保证系统稳定和安全的前提下提供服务,避免应用程序恣意横行。
fopen,fread,fwrite,fseek等C语言提供的读取文件的函数库,实现是以对应的系统调用为基础。
//设置绝对地址为0x67a9的整形变量的值为0xaa66
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66
//头文件 #include
#ifdef _M_ALPHA
typedef struct {
char *a0; //pointer to first homed integer argument
int offset; //byte offset of next parameter
} va_list;
#else
typedef char * va_list;
#endif
_M_ALPHA 是指 DEC ALPHA(Alpha AXP)架构。所以一般 va_list 定义变量为字符指针
INTSIZEOF宏,获取类型占用的空间长度,最小占用长度为int的整数倍
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
VA_START宏,获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数)
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型):
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
VA_END宏,清空va_list可变参数列表:
#define va_end(ap) ( ap = (va_list)0 )
typedef int * myva_list;
#define va_start(ap, A) ap = &(A);
#define va_arg(ap, T) *(T *)(ap = (char *)ap + sizeof(T))
#define va_end(ap) ap = (void *)0
#include
#include
#include
void myprintf(char *fmt, ...){
va_list ap;
int d; double f; char c;
char *s;
char flag;
va_start(ap,fmt);
while(*fmt){
flag = *fmt++;
if(flag != '%'){
putchar(flag);
continue;
}
flag = *fmt++;//记得后移一位
switch(flag){
case 's':
s = va_arg(ap,char*);
printf("%s", s);
break;
case 'd': /* int */
d = va_arg(ap, int);
printf("%d", d);
break;
case 'f': /* double*/
d = va_arg(ap,double);
printf("%d", d);
break;
case 'c': /* char*/
c = (char)va_arg(ap,int);
printf("%c", c);
break;
default:
putchar(flag);
break;
}
}
va_end(ap);
}
/*ANSI标准形式的声明方式,括号内的省略号表示可选参数*/
int demo(char *msg, ...)
{
va_list argp; /*定义保存函数参数的结构 */
int argno = 0; /*参数个数 */
char *para; /*存放取出的字符串参数 */
va_start(argp, msg);
while(1){
para = va_arg(argp, char *); /*取出当前的参数,类型为char *. */
if(strcmp(para, "\0") == 0) /*采用空串指示参数输入结束 */
break;
printf("Parameter #%d is: %s\n", argno, para);
argno++;
}
va_end(argp); /*将argp置为NULL */
return 0;
}
void main(void){
char str[10] = "linuxcode";
int i = 1024;
double f = 3.1415926;
char c = 'V';
myprintf("string is:%s, int is:%d, double is:%f, char is :%c\n", str, i, f, c);
demo("DEMO", "This", "is", "a", "demo!", "333333", "\0");
}
有些信息在存储时,并不需要占用一个完整的字节,而只需占用一个或几个二进制位。
例如在存放一个开关变量时,只有0和1 两种状态,用一位二进位即可。
为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。
所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。
每个域有一个域名,允许在程序中按域名进行操作。
1. 一个位域必须存储在同一个字节中,不能跨两个字节。
如一个字节所剩空间不够存放一位域时,应从下一单元起存放。也可以有意使某位域从下一单元开始(空域)
struct bs{
unsigned int a:1;
unsigned int :0; /*空域*/
unsigned int b:1; /*从下一单元开始存放*/
unsigned int c:1;
} data;
sizeof(data) = 8
2. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
struct k {
int a:1
int :2 /*该2位不能使用*/
int b:3
int c:2
} data;
sizeof(data) = 4
3. 字节内也是有大小端问题,与字节中的大小端类似。
在使用中为了兼容大小端,结构体的定义总是区分了大小端情况:
结构体A描述了在一个字节(byte)内,位域大小端的定义方式——小端将字节内的定义顺序翻转即可;
结构体B描述了在一个字(word)内位域的定义方式——小端将一个字内的定义顺序全部翻转,
在使用前需要先调用ntohl宏进行转换。
struct A {
#ifdef BIG_ENDIAN
char:4;
char:4;
#else
char:4;
char:4;
#endif
}
struct B {
#ifdef BIG_ENDIAN
int a:1;
int b:2;
int c:3;
int d:4;
int e:5;
int f:6;
int g:11;
#else
int g:11;
int f:6;
int e:5;
int d:4;
int c:3;
int b:2;
int a:1;
#endif
};
//位操作
//设置变量的bit3
#define BIT3 (0x1<<3)
static int a;
void set_bit3() {
a |= BIT3;
}
//清除变量的bit3
void clear_bit3() {
a &= ~BIT3;
}
Stack frame,简称为帧。函数是C语言基本的模块,当函数执行时,函数的相关信息将保存到进程的栈空间中。与函数执行相关的信息(函数调用的位置、调用参数等)及函数的局部变量等。
当发生一个函数调用时,与函数有关的信息都将保存到栈空间中的一个数据块中。一个函数就对应栈空间中的一个数据块,通常把用来保存函数调用相关信息的数据块称做栈帧。
当程序因为断点暂停执行时,可以通过相应的GDB命令查看栈帧中的这些信息,了解函数的执行情况。
当被调试程序开始执行以后,GDB就会存在一个选定的栈帧,通常情况下这个栈帧也就是当前下在执行的函数的栈帧,也可以通过GDB命令来选定其他栈帧。
当使用print命令查看局部变量时,就是在当前选定的栈帧中查找这个局部变量。
当程序开始执行时,在栈空间中一开始只存在一个栈帧,main函数,main函数的这个栈帧是外层栈帧。函数的调用层次比较多时,就会形成一个连续的栈帧。
当一个新的函数被调用时,就会开成一个新的栈帧,而当一个被调用的函数返回后,就会从栈空间中去掉这个栈帧。