补充C语言

  • Post author:
  • Post category:其他


1.关键字

前言:



  • C90


    一共有


    32


    个关键字,
  • C99比C90多了5个关键字,但主流的编译器对C99关键字支持的不是特别好,

    • 所以后面主要以C90的32个关键字为标准

1.1认识auto关键字

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h> 
int main()
{
	int i = 0;
	auto int j = 0;
	return 0;
}
  • 一般在代码块中定义的变量,即


    局部变量


    ,默认都是


    auto


    修饰的,不过


    一般省略

1.2认识register关键字

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int main()
{
	register int a = 0;
	printf("&a = %p\n", &a);
	return 0;
}

  • register


    会尽量


    将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的
  • 现在的编译器已经很智能化了,它能够


    自主的决断


    是否将变量放入CPU寄存器
  • 被register修饰的变量,

    不能取地址

    • 因为已经放在寄存区中了,地址是内存相关的概念

1.3认识extern关键字

  • 被extern修饰的变量或者函数表示为


    声明外部属性



  • 注意:


    extern int g_val = 11;是错误的,extern只能声明,

    不能定义,初始化,赋值等等

  • 编程好习惯:

    声明变量或函数的时候,都


    带上extern

    • 比如:


      extern


      int g_val = 100;


      extern


      void show();

1.4认识static关键字

  • 被static修饰的全局变量或函数,只能在本文件内被访问,不能被外部其他文件


    直接


    访问
  • 被static修饰的局部变量,会更改局部变量的


    生命周期


    ,将其放在


    静态区


  • 编码好习惯

    :函数名的


    首字母大写


    • 比如:函数名My_strlen

1.5细节sizeof关键字

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int a = 10;
	printf("%d ", sizeof(a));
	printf("%d ", sizeof a);
	printf("%d ", sizeof(10));
	printf("%d ", sizeof 10);
	printf("%d ", sizeof(int));
	// printf("%d ", sizeof int);
	return 0;
}
  • sizeof + 变量或常量,有无括号都行,但是


    不能计算变量类型


  • 编程好习惯:

    在定义全局变量或函数的时候,名字应表示为g_名字

    • 比如:int


      g_


      val = 10;void


      g_


      show()

1.6认识bool(_Bool)关键字


C99引入了_Bool类型

(你没有看错,


_Bool就是一个类型


,不过在新增头文件stdbool.h中,被重新用宏写成了bool,为了保证C/C++兼容性)。

  • bool用宏重新封装了_Bool


编程好习惯



  • 左边的代码风格


    明显 比 右边的代码风格 更加


    优秀

  • 右边的代码会让人产生误解,认为是在


    判断


    if条件中的flag

    是不是等于零

1.7细节double关键字

double类型

变量与





零值





进行比较

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.50lf\n", x);
    printf("%.50lf\n", y);
    if ((x - 0.9) == y) {
        printf("you can see me!\n");
    }
    else {
        printf("oops!\n");
    }
    return 0;
}

  • 0.1在double类型中存储的其实会比0.1大一点点

    • 因为存储的时候出现了


      精度损失



如果double中的0.1



实际的0.1的

绝对值

小于

DBL_EPSILON

,就认为它几乎等于实际的

0.1

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<float.h>
#include<math.h>
int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.50lf\n", x);
    printf("%.50lf\n", y);
    if (fabs((x - 0.9) - y)< DBL_EPSILON) {
        printf("you can see me!\n");
    }
    else {
        printf("oops!\n");
    }
    return 0;
}

  • 这段代码中的


    fabs是求double


    类型绝对值的函数


    ,abs是求int类型


    绝对值的函数
  • fabs((x – 0.9) – y)<


    DBL_EPSILON,



    注意

    这里不能写等于,

1.8细节switch,case关键字

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
    int num = 1;
	int b = 1;
	switch (num)
	{
	case 1:
		// int a = 1;// error
		break;
	case 2:
		b = 1;
		break;
	case 3:{
			int c = 1;
		}
		break;
	default:
		break;
	}
    return 0;
}



  • case中不能定义变量


    ,如果要在case中定义变量必须加上


    代码块{}

  • 编程好习惯:case匹配时,尽量把

    常见的情况放在前面

1.9细节continue关键字

  • 在for循环中continue是跳到改变


    循环变量


    的位置
  • 编程好习惯:


    双层for循环


    的时候,尽量保证


    外小内大(范围)


    ;

1.10 细节void关键字

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
    // void a = 0;// 不允许使用不完整的类型
    printf("%d", sizeof(void));
    return 0;
}


  • void


    本身就被编译器解释为

    空类型,强制的不允许定义变量
  • 在linux中


    void的大小是1


    ,而在vs2019中


    void的大小是0

1.11细节return关键字

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<windows.h>
int SumAdd() {
	int sum = 0;
	for (int i = 1; i <= 10; i++) {
		sum += i;
	}
	return sum;
}
int main()
{
	int sum = SumAdd();
	printf("%d", sum);
	return 0;
}

  • return返回的时候,会通过


    寄存器的方式



    ,返回给函数调用方

    ,及时没有接收也一样

1.12细节const关键字


  • 在C语言中,

    const机制是通过编译器检查实现的

    ,它标记const变量不能被直接修改,但并未限制const变量的地址的引用,



  • 只要变量的地址存在被引用





    可能


    ,就说明该变量是可以


    通过指针被间接修改


    的。
  • 所以,只要我们能保证程序在编译过程不出错,那么在程序的运行过程中我们便可以通过

    指针间接修改该const修饰的变量的值

1.13细节struct关键字


  • 空结构体的大小,在

    不同的编译器下是不同的

1.14细节typedef关键字

  • 存储关键字有:auto,extern,register,static,typedef
  • 存储关键字,


    不可以同时出现


    ,也就是说,在一个变量定义的时候,只能有一个

    深入理解变量的左右值


  • 左值表示


    空间


    ,右值表示


    内容

  • 任何一个变量名,在

    不同的应用场景中,代表不同的含义


深入理解变量内容的存入和取出



signed


int b = -10;


-10存入:

  • -10的原码:

    1

    000 0000 0000 0000 0000 0000 0000 1010
  • -10的补码:

    1

    111 1111 1111 1111 1111 1111 1111 0101
  • -10的补码:

    1

    111 1111 1111 1111 1111 1111 1111 0110


-10取出:(有符号,需要再转换一下)


  • 1

    000 0000 0000 0000 0000 0000 0000 1010



unsigned


int d = -10;


-10存入:

  • -10的原码:

    1

    000 0000 0000 0000 0000 0000 0000 1010
  • -10的补码:

    1

    111 1111 1111 1111 1111 1111 1111 0101
  • -10的补码:

    1

    111 1111 1111 1111 1111 1111 1111 0110


-10取出:


  • 1

    111 1111 1111 1111 1111 1111 1111 0110

案例

  • 关键在于到底是%d打印,还是%u打印(数据究竟是以


    有符号


    还是


    无符号


    的形式取出的)

总结:

  1. 存:字面数据必须先转成补码,再放入空间当中,所谓符号位,完全看数据本身是否携带+-         号。

    和变量是否有符号无关!
  2. 取:数据一定要先看


    变量本身类型


    ,然后才决定要不要看最高符号位。如果不需要,直接二         进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪         里,又要明确

    大小端

    )


深入理解char类型中的-128

-128的原码:

1

1000 0000

-128的反码:

1

0111 1111

-128的补码:

1

1000 0000

char类型只有8个bit位,所以-128存入char中的时候会发生


截断

  • -128在char中为1000 0000,他们

    规定这个1000 0000就当作-128
  • 注意1000 0000和0000 0000


    都可以表示0


数据类型的取值范围:-2^(n-1)到2^(n-1)-1

  • 上面这个公式中的n表示的是


    数据类型的bit位

  • 比如char取值范围:[-2^7,2^7-1] 就是 [-128,127]
  • 比如short取值范围:[-2^15,2^15-1]
  • 比如int取值范围:[-2^31,2^31-1]

2.符号

++和–

  • 不管是前置++,还是后置++,都是


    通过寄存器


    来改变值的,
  • 注意: 在


    没有接收方


    的时候,

    前置++和后置++是一样的


深度理解取余


/


取模运算

2.1取整运算

  1. 四舍五入取整(


    round


    )
  2. 向负无穷取整(


    floor


    )
  3. 向正无穷取整(


    ceil


    )
  4. 向0取整(


    trunc


    )
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
#include <math.h> 
int main() 
{ 
	const char * format = "%.1f \t%.1f \t%.1f \t%.1f \t%.1f\n"; 
	printf("value\tround\tfloor\tceil\ttrunc\n"); 
	printf("-----\t-----\t-----\t----\t-----\n"); 
	printf(format, 2.3, round(2.3), floor(2.3), ceil(2.3), trunc(2.3));
	printf(format, 3.8, round(3.8), floor(3.8), ceil(3.8), trunc(3.8));
	printf(format, 5.5, round(5.5), floor(5.5), ceil(5.5), trunc(5.5));
	printf(format, -2.3, round(-2.3), floor(-2.3), ceil(-2.3), trunc(-2.3));
	printf(format, -3.8, round(-3.8), floor(-3.8), ceil(-3.8), trunc(-3.8));
	printf(format, -5.5, round(-5.5), floor(-5.5), ceil(-5.5), trunc(-5.5)); 
	return 0;
}

  • 在vs2019中的取整规则是


    向0取整

2.2负数取模


取模定义

如果a和d是两个自然数,


d非零


,可以证明存在两个唯一的


整数 q 和 r

满足


a = q*d + r


, q 为整数,

且0 ≤ |r| < |d|其中,q 被称为商,r 被称为余数。

  • 在C语言中:-10=(-3)*3+(-1),因为C语言中是

    向0取整

    所以商是-3,余数是-1,也叫

    负余数
  • 在Python中: -10=(-4)*3+2,因为Python中是

    向负无穷取整

    所以商是-4,余数是2,也叫

    正余数
  • 所以,在不同语言,同一个计算表达式,负数“取模”结果是不同的


2.3取余和取模的关系



取余本质


:尽可能让商,进行


向0取整



取模本质


:尽可能让商,向


负无穷方向取整

对任何一个

大于0

的数,对其进行0向取整和-∞取整,取整方向是一致的。故取模

等价

于取余

对任何一个

小于0

的数,对其进行0向取整和-∞取整,取整方向是相反的。故取模

不等价

于取余

  • C中%,本质其实是

    取余

  • Python中%,本质其实是

    取模
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h> 
int main() 
{ 
	printf("%d\n", -10 / 3); //结果:-3 
	printf("%d\n\n", -10 % 3); //结果:-1 为什么? -10=(-3)*3+(-1) 
	printf("%d\n", 10 / -3); //结果:-3 
	printf("%d\n\n", 10 % -3); //结果:1 为什么?10=(-3)*(-3)+1 
	return 0; 
}

  • 明显结论:如果不同符号,余数的求法,

    参考之前定义

    。而


    余数符号,与被除数相同

3.预处理过程

3.1宏定义充当注释符号

  • 由上面的结果可以得到: 预处理期间先执行


    去注释


    ,然后再进行


    宏替换

3.2宏定义替换多条语句


  • 为了解决else匹配的问题,这里引入了

    do-while-zero结构

3.3宏定义的实际范围


  • 在源文件的


    任何地方


    都可以定义宏,有效范围是

    从定义处向下都有效


  • #undef:


    是取消宏定义

3.4详细条件编译



  • #ifdef


    宏: 定义了,就执行


  • #ifndef


    宏: 没定义了,就执行

多条件下使用条件编译


  • #if #elif #else #endif


    来解决多条件的情况


  • 补充:


    #if defined等价于#ifdef,#if !defined等价于#ifndef

3.5头文件的展开

  • 头文件的展开就是


    拷贝库函数


    到当前文件
  • 使用


    #ifndef





    #pragma once


    都可以解决头文件被重复包含的问题

3.6了解

#error

预处理

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
//#define __cplusplus 
int main() 
{
	#ifndef __cplusplus 
	#error 老铁,你用的不是C++的编译器哦 
	#endif
	return 0; 
}

  • #error可以

    自定义编译报错

3.7 了解

#line

预处理

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int main() 
{ 
	//C预定义符号,代表当前文件名和代码行号 
	printf("%s, %d\n", __FILE__, __LINE__); 
#line 60 "hehe.h" //定制化完成
	printf("%s, %d\n", __FILE__, __LINE__); 
	return 0; 
}
  • #line可以 定制化


    文件名称





    代码行号

3.8

#

运算符

  • 将参数符号s对应的文本内容,

    转换成为”字符串”

3.9

##

预算符

  • 3.14e3是指数的科学计数法,这个运算符会将##相连的符号和


    合成


    一个新的字符,

4.指针和数组

4.1 理解指针和指针变量

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int* p = NULL;
	p = (int*)0x1234;
	int* q = p;
	return 0;
}
  • 指针变量:


    空间(左值)


    +


    内容(右值:地址)

  • 第一个p使用的是


    变量p的空间

  • 第二个p使用的是


    变量p的内容,


    也就是0x1234,此时指针==指针变量

4.2 理解指针变量的解引用


  • 0x1234如果是赋给p指针变量的

    空间

    是不会报错的,
  • 由此推断出0x1234是赋给p指针


    变量的内容


    ,
  • 指针变量进行解引用,使用的是

    指针变量的右值(内容)

4.3 了解变量地址

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int a = 10;
	printf("%p\n", &a);
	return 0;
}



  • 栈随机化技术


    : 使得每次重新编译打印的时候都地址都

    不一样


4.4 数组内存布局

  • 局部变量都是在栈区上面的,而栈区的使用习惯是

    先使用高地址再使用低地址
  • 在开辟空间的角度,不应该把数组认为成一个个独立的元素,


    要整体开辟,整体释放

4.5理解数组传参

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void show(int pr[])
{
	int i = 0;
	for (i = 0;i < 10;i++){
		printf("%d ", *(pr + i));
		//printf("%d ",pr[i]);
	}
}
int main() 
{ 
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	show(arr);
	return 0; 
}
  • 为了解决拷贝问题, 所有的数组,传参都会发生降维,都会


    降维




    指向内部元素类型的指针

4.6 理解指针和数组访问元素的相似性

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void show(int* pr)
{
	int i = 0;
	for (i = 0;i < 10;i++){
		printf("%d ", *(pr + i));
		//printf("%d ",pr[i]);
	}
	printf("\n");
}
int main() 
{ 
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	show(arr);
	for (i = 0;i < 10;i++) {
		printf("%d ", arr[i]);
		//printf("%d ", *(arr + i));
	}
	return 0; 
}
  • 虽然指针和数组都可以


    通过*和[] 进行解引用


    ,但他们的

    寻址方案是完全不一样的
  • 这样设计就可以减低编程的难度,不用来回切换

4.7 了解数组定义

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int a = 0;
	float b = 0.0f;
	int arr[10] = { 0 };
	//int [10] arr = { 0 };
	return 0;
}
  • 在C语言中数组的定义:


    int arr[10]

  • 在C#语言中数组的定义:


    int [10] arr

4.8 理解数组元素

  • 数组里的

    元素是数组类型的一部分

4.9 数组经典题

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int a[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x\n", ptr1[-1], *ptr2);
	return 0;
}

  • 大多数机器都是


    小端


    机,存的时候用小端,取的时候也用小端

4.10 了解多维数组结构


  • 所有的数组都可以当成


    “一维数组”


    ,多维数组就相当于一维数组不停的


    套娃

5. 内存管理


5.1 验证

C

程序动态地址空间分布


5.2详谈内存越界问题

案例一

  • 越界不一定报错

案例二

  • 对于数值越界访问的检查,是一种


    抽查


    机制

案例三

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
#include <stdlib.h>
int main()
{
	while (1) {
		int* p = malloc(1024);
	}
	return 0;
}

  • 程序退出,内存泄漏问题就不在了

    ,被自动回收了
  • 内存泄漏问题对于那些永远不会主动退出的程序,比如:


    操作系统,杀毒软件,服务器等,影响大、


5.3


C


中动态内存





管理





体现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
#include <stdlib.h>
int main()
{
	char* p = (char*)malloc(sizeof(char) * 10);
	printf("before:%p\n",p);
	free(p);
	printf("after:%p\n", p);
	return 0;
}

  • 其实释放的字节会比实际上10个字节多得多,申请的一定不止10字节
  • malloc申请空间的时候,


    系统给你的其实更多


    ,而多出来的那部分,记录了这次申请的更详细信息,
  • free的释放,相当于取消关系,使之后的p无法再使用

6. 函数栈帧


6.1 样例代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int MyAdd(int a, int b) 
{ 
	int c = a + b;
	return c; 
}
int main() 
{ 
	int x = 0xA;
	int y = 0xB; 
	int z = MyAdd(x, y);
	printf("z = %x\n", z);
	return 0; 
}

6.2

认识相关寄存器


寄存器名称

作用

eax

通用寄存器,保留临时数据,常用于返回值
ebx 通用寄存器,保留临时数据


ebp



栈底寄存器



esp



栈顶寄存器

eip 指令寄存器,保存当前指令的下一条指令的地址

6.3

认识相关汇编命令


汇编命令

作用
mov 数据转移指令
push 数据入栈,


同时esp栈顶寄存器也要发生改变

pop 数据弹出至指定位置,


同时esp栈顶寄存器也要发生改变

sub 减法命令
add 加法命令
call 函数调用,

1. 压入返回地址 2. 转入目标函数
jump

通过修改eip,转入目标函数


,进行调用
ret 恢复返回地址,压入eip,类似pop eip命令

step1: main函数也是要被调用的


  • 其实main()函数是在


    _tmainCRTStartup


    函数中调用的,

    • 创建main( )函数的栈帧,
    • 完成状态寄存器的保存,
    • 堆栈寄存器的保存,
    • 函数内存空间的初始化。

step2:main函数栈帧创建

  • 寄存器


    ebp


    指向当前的栈帧的底部



    高地址


  • 寄存器


    esp


    指向当前的栈帧的顶部


    (低地址)

  • 第一张图片的那2条mov汇编是在main函数的栈帧中,

    开辟变量x和变量y,并赋值
  • 第二张图片的那4条汇编做了一件事->形参实例化(且形参实例化是向从

    最右边

    开始实例化的)

step3:调用Myadd函数->压栈

  • call这条汇编主要做

    1. 压入返回地址 2. 转入目标函数
  • 压入返回地址


    b0 53 8f 00


    是为了以后能找到

step4:创建Myadd函数的栈帧->入栈

  1. 执行push汇编命令,


    esp的指向会变

  2. 执行mov汇编命令
  3. 执行sub汇编命令(


    开辟空间的大小和里面的代码有关


    )

step5:释放Myadd函数的栈帧->弹栈

  • mov汇编命令:

    就可以说Myadd的函数被释放了
  • pop汇编命令:

    会把ebp指向main函数的栈底,esp也会变
  • ret汇编命令:


    会把b0 53 8f 00写回eip中

step6: Myadd函数结果返回

  • add汇编命令:

    会把esp+8

  • add汇编命令:

    会得到eax中的值
  • Myadd函数的返回值是通过


    寄存器


    来返回的


总结:

  1. 调用函数,需要先形成

    临时拷贝,形成过程是从右向左的

  2. 临时空间的开辟,是在


    对应函数栈帧内部开辟的

  3. 函数调用完毕,栈帧结构被释放掉

  4. 临时变量具有临时性的本质:

    栈帧具有临时性

  5. 调用函数是有成本的,成本体现在时间和空间上,本质是

    形成和释放栈帧有成本

  6. 函数调用,因拷贝所形成的临时变量,变量和变量之间的位置关系是有规律的

编程好习惯

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int MyAdd(const int* a, const int* b)
{
	if (a == NULL || b == NULL) {
		printf("NULL error\n");
		return 0;
	}
	int c = *a + *b;// 尽量用空格缩进
	return c;
}

int MySub(const int* a, const int* b)
{
	if (a == NULL || b == NULL) {
		printf("NULL error\n");
		return 0;
	}
	int c = *a + *b;// 尽量用空格缩进
	return c;
}

int main()
{
	int num1 = 1, num2 = 2;
	// MyAdd(NULL, NULL);
	MyAdd(&num1, &num2);
	MySub(&num1, &num2);
	return 0;
}
  • 函数与函数之间


    空一行

  • 如果参数是


    输入型的指针


    ,则应在类型前加


    const


    以防止该

    指针在函数体内被意外修改
  • 缩进的时候尽量用


    空格缩进


    ,如果用tab的话可能会发生排版的乱序



  • assert


    检测空指针的时候,只会在


    Debug


    下才有效,所以建议

    用if判断空指针的情况

7.可变参数列表

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
#include <stdarg.h>

int FindMax(int num,...)
{
	va_list arg;//定义可以访问可变参数部分的变量,其实是一个char*类型
	va_start(arg, num);//使arg指向可变参数部分
	int max = va_arg(arg, int);//根据类型,获取可变参数列表中的第一个数据
	int i = 0;
	for (i = 0; i < num - 1; i++) {
		int curr = va_arg(arg, int);
		if (max < curr) {
			max = curr;
		}
	}
	va_end(arg);
	return max;
}

int main()
{
	int max = FindMax(5, 11, 22, 33, 44, 55);
	printf("max=%d\n", max);
	return 0;
}

  • 参数列表中


    至少有一个命名参数


    。如果连一个命名参数都没有,就无法使用


    va_start

  • 如果在 va_arg 中指定了


    错误的类型


    ,那么其后果是不可预测的。
  • 可变参数必须


    从头到尾


    逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
  • 实际传入的参数如果是


    char,short,float


    ,编译器在编译的时候,


    会自动进行提升

  • 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行

7.1可变参数列表原理

va_list 和 va_end

  • va_list : 定义可以访问可变参数部分的变量,其实是


    一个char*


    类型
  • va_end : 相当于把arg指针


    置成空

va_start


  • va_start :


    使arg指向可变参数部分

va_arg


  • va_arg:


    根据类型,获取可变参数列表中的第一个数据
  • 这里的arg指针减去4字节,再加上4字节,可以说设计的


    非常巧妙

#define _INTSIZEOF(n)

((sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1))

  • 是一个求


    最小对齐数


    的宏,这是4的倍数

理解一:4的倍数

  • 既然是4的最小整数倍取整,那么本质是:x=4*m,
  • m是具体几倍对7来讲,m就是2,对齐的结果就是8,而m具体是多少,取决于n是多少
  • 如果n能整除4,那么m就是n/4,如果n不能整除4,那么m就是n/4+1
  • 由此产生了一种写法:


    4的倍数等于




    (n+3)/4,




    也就是




    ( n+sizeof(int)-1) )/sizeof(int)

理解二:最小4字节对齐数

  • 搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,
  • 就变成了

    4


    字节对齐数等于

    ((n+4-1)/4)*4

    也就是

    ((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小]

理解三:理解源代码中的宏

  • ((n+4-1)/4)* 4,设w=n+4-1,表达式就变成了(w/4)*4,
  • 其中一个数除4等价于二级制位右移2位,一个数乘4等价于二级制位左移2位

  • 简洁版:

    (n+4-1) & ~(4-1)


  • 原码版:

    ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1) ),

8.简单了解

命令行参数


  • main函数也是可以传参的,

  • 第一个参数:




    argc 是个整型变量

    ,表示命令行参数的个数(含第一个参数)。

  • 第二个参


    数:




    argv 是个字符指针的数组

    ,每个元素是一个字符指针,指向一个字符串。这些字符串就是命令行中的每一个参数(字符串)。
  • argv数组的最后一个元素存放了一个


    NULL


    的指针。



版权声明:本文为LYC_462857656原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。