C语言开发

变量 a
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的寄存器中,加快其存储速度。

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 <stdio.h>
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 <stdio.h>

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 <stdio.h>
#include <stdlib.h>
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 <stdarg.h>
#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 <stdio.h>
#include <string.h>
#include <stdarg.h>

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函数的这个栈帧是外层栈帧。函数的调用层次比较多时,就会形成一个连续的栈帧。
当一个新的函数被调用时,就会开成一个新的栈帧,而当一个被调用的函数返回后,就会从栈空间中去掉这个栈帧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春夏与冬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值