Notes-C

C语言内存地址

1
2
3
4
5
6
7
8
9
10
11
12
13
// 命令行执行
// .obj文件为链接文件
// cl /c /W4 /P hello.c -编译
// link hello.obj -链接
#include <stdio.h> //环境变量寻找依赖,""当前目录寻找
int main()
{
int n = 999; // 0x000003e7 19ff2c(地址,全称0019ff2c) -> E7 03 00 00 (值)
// 999(十进制) -> 3e7(十六进制)
printf("Hello world!%p:%d\r\n",&n,n); //打印内存地址
getchar();
return 0;
}

WinHex 打开内存文件

查找16进制:Alt+Ctrl+F
搜索下一个:F3

到特定的偏移地址,Seek Logical Address/Go to Virtual Address

VC++6.0固定变量内存地址,最新版的VC是随机地址

VC98/CRT/SRC/CRT0.C->mainCRTStartup

内存存储机制

补码(整数和小数的表示)

整数

内存中存储的都是补码形式,先判断正负数,再对两者之间进行转换
比如:winHex里面的十六进制跟变量十进制之间的转换

二进制的 + - * / 原理

1
2
3
4
5
6
  A - B
= A + (0x100-B) - 0x100
B + ~B = ff
B + ~B + 1 = 0x100
~B + 1 = 0x100 - B //求补运算
= A + neg(B) - 0x100 //进位丢失 010进位10

求补运算是一种运算
补码是一种编码
补码规定了数据的读写双方必须做到:
1、最高有效位是符号位,0表示正,1表示负
2、当数据是正数的时候,其余各种直接存储其数值
3、当数据为负数的时候,其余各种存储其求补后的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
// 110100
// 0 0110100
// 0011 0100
// 3 4
int n = 52;// 34 00 00 00
// 110100
// 1 0110100
// 1 1001011 + 1 取反+1
// 1 1001100
// 1100 1100
// c c
int m = -52;// cc ff ff ff
printf("%x\r\n",&n);
getchar();
return 0;
}

公式:补码=反码+1

十进制整形在内存中的存储:

1、转化为二进制
2、正数则直接按照二进制转换为十六进制
3、负数则除符号位,取反+1,再转换为十六进制
4、按照内存4个字节,一个字节8位?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
计算 34-34
// 34
// 100010
// 0 0100010
// 0010 0010
// 2 2
=> 22 00 00 00
// -34
// 100010
// 1 0100010
// 1 1011101 + 1
// 1 1011110
// 1101 1110
=> de ff ff ff

0x86单字节有符号数的十进制真值是什么?
// 就是求内存中十六进制表示是86的值,实际变量定义的十进制数是多少
// 1000 0110
// 1 0000110
// - 1111001 + 1 补码转化为正常二进制,也是除符号位外取反+1
// - 1111010
// - 0111 1010
// - 7 A
// - 7*16 + 10 = -122

定义变量为-122,用winHex去寻找地址看是不是86


0x80呢?
// 1000 0000
// 1 0000000
// - 1111111 + 1
// - 1000 0000
// - 8 0
// - 8*16
// -128

PS:这是在winHex里面的表现形式,十六进制表示,其余程序不一定是这样
十进制整数在winHex的表示,直接把十进制转化为十六进制,小端排序即可

1
2
3
4
5
十六进制中,大于等于8的都是负数 
1000 因为存储的都是补码形式

看首位是否<8,<8说明是正数,≥8说明是负数。
原理:临界值为7fffH,我们知道首位7的二进制码为0111,而0111的首位是0,说明是正数。而像8000H首位8二进制码为1000,首位为1,就变成负数了。

小数

二进制存储小数原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1、固定分解
二进制0.1表示为十进制的0.5,0.01表示为0.25,0.001表示为0.125
因此,十进制的小数转化为二进制如下:
0.375 < 0.5 => 0
0.375 > 0.25 => 1
0.125 = 0.125 => 1
因此 0.375(十进制) = 0.011(二进制)

2、乘2取余
乘2如果小于1说明原值小于0.5,即取0
0.375 * 2 = 0.75 < 1 => 0
0.75 * 2 = 1.5 > 1 => 1
0.5 * 2 = 1 == 1 => 1
因此 0.375(十进制) = 0.011(二进制)

0.6就没法精确

实际存储
总共4个字节32位,1个字节8位即一个十六进制
一位放正负符号、一个字节(8位)放小数点位置,其余23位放值。注:64位有11位表小数点位置
32位 = 1 + 8 + 23
32位4个字节,小数形式十六进制存储形式如下:
S EEEEEEEE DDDDDDDDDDDDDDDDDDDDDDD

案例计算:
23.625(十进制)
=> 10111.101(二进制) 有小数转十六进制的形式
=> 1.0111101 * 10^4
S(正负符号位) EEEEEEEE(小数点位) DDDDDDDDDDDDDDDDDDDDDDD(值,补0)
小数点位存储8位,大小为,0-127-255,转化为 -128-0-127
即原来0-4-|127|-255变成-128-|0|-4-127
原先0-255表示4是 00000100
而后-128-127表示4是 4+127 = 128+3 = 10000011
=>0 10000011 01111010000000000000000
=>01000001101111010000000000000000
=>0100 0001 1011 1101 0000 0000 0000 0000
=>4 1 B D 0 0 0 0
=>00 00 BD 41
1
2
3
4
5
6
7
8
9
题目1:
将895.75转换为float类型的16进制形式,895 = 1101111111(B)
1101111111.11
1.10111111111 * 10^9
9+127 => 128+8 => 10001000
0 10001000 10111111111000000000000
0100 0100 0101 1111 1111 0000 0000 0000
4 4 5 F F 0 0 0
00 F0 5F 44
1
2
3
4
5
6
7
8
9
10
11
题目2:
已知地址0013FF74内容为:00 68 45 44,系统字节顺序为小尾方式,如果这个地址是存放flaot变量,那么其10进制真值为多少?
44 45 68 00
0100 0100 0100 0101 0110 1000 0000 0000
0 10001000 10001010110100000000000
1 10^9
+1.100010101101 * 10^9
+1100010101.101
+2^9 + 2^8 + 2^4 + 2^2 + 1 + 0.5 + 0.125
+512+256+16+4+1+0.5+0.125
+789.625
1
2
3
4
5
6
7
8
9
10
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
float f = 23.625f;
printf("%08x\r\n",&f);
getchar();
return 0;
}

关于字节问题

4个字节,一个字节8位,每8位相当于一个十六进制 0000 0000

winHex是双字节存储,每个地址对应的是两个十六进制数

随机数

VC98/CRT/SRC/RAND.C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
// 测试程序什么时候退出
int main()
{
int n = 0;
srand((unsigned)time(NULL));
while(n<10)
{
printf("%x\r\n",rand()); //移除括号外,开销大,printf开销大
n++;
}
return 0;
}
1
2
3
4
5
6
7
# run.bat
del *.obj
del *.exe
cl /c /W4 /WX 2.c
link 2.obj
2.exe
pause

C语言基础语法分析

1、数值和字符串

‘a’表示为ASCLL,输入到变量地址要&n,输入指定地址直接n

“a”表示为字符串,默认取首地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
char szBuf[20] = {0}; //vc6.0默认在19ff2c地址开始存储
scanf("%s",szBuf); //scanf存在越界情况
//scanf("%19s",szBuf); 这个19也可以改,如果有条件
//scanf("%19[0-9]s",szBuf);
//scanf("%19[0,5,9,a]s",szBuf);
//scanf("%19[^8]s",szBuf);
// 输入12341234123412341234000
// 此处是以字符串的形式输入,0=>30,1=>31,2=>32,3=>33,4=>34
system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
//2、输入整数,内存存储的地址
int n; //未初始化,残留值地址
scanf("%d",n);
//地址默认在19ff2c开始存储,但此处存储的是一个地址,残留的地址,要根据地址去跳转
/*2.1、 初始化位0,0是保留地址,可以去winxp看详细报错
int n = 0; 会报错,
scanf("%d",n);
*/
/*2.2、 定义短字节整形,%d输入,造成覆盖
short int ary[2] = {666,999}; 定义数组时,即把值放在19ff2c里
scanf("%d",&ary[0]); 此处指定存储地址为数组的第一个值地址
此处把666和999放到固定的19ff20那一行,"%hd"
输入999,理论上是修改666的值,但4字节输入,2字节存储,导致后面覆盖成0
*/
system("pause");
return 0;
}

C0000005内存访问异常

scanf怎么判断指定输入的是地址还是值,默认都是存储在19ff20这一行

结论:

scanf后续指定的就是地址

  1. 有初始化值,默认存储在19ff2c,后续的输入是覆盖变量值(整数、字符等)
  2. 无初始化值,取残留值,即默认地址存储的是残留地址,残留地址存储的才是值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
int n = 0x19ff2c; // 此处存储地址默认在19ff28
int m = 0; // 此处存储地址默认在19ff2c
// printf("%p:%d",&n,n); 0019FF28:1703724
scanf("%d",n); // 输入999,此处把99输入到n对应的地址
printf("%08x\r\n",n);
printf("%08x\r\n",m);
system("pause");
return 0;
}
/*结果
999
0019ff2c
000003e7
*/

不同于printf,默认变量定义或从scanf获取值时,就已经开辟内存

m = 0x19ff2c时,是存储在19ff28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
int n = 0x19ff2c;
int m = 0;
int i = 0;
printf("n address:%p\r\n",&n); //打印内存地址
printf("m address:%p\r\n",&m); //打印内存地址
printf("i address:%p\r\n",&i); //打印内存地址
getchar();
return 0;
}
/*
n address:0019FF24
m address:0019FF28
i address:0019FF2C
*/

结论:

  1. vc++中变量存储地址从19ff2c开始
  2. 但是有多个变量,地址是往前排,比如有两个变量,第一个变量地址是 19ff28 ,第二个是19ff2c

2、新建C项目(新建&调试)

新建MFC项目控制台项目(基于 Microsoft 基础类 (MFC) 库的 Windows 可执行应用程序)

create -> Win32 Console Application->Helloworld

快捷键(F5、Ctrl+F7、F9、F10、F11),反汇编

F5:编译、链接、调试、运行

Ctrl+F7:编译不链接

F7:编译、链接

F9:打断点

F10:执行下一步,跳过循环或函数

F11:执行下一步,进入循环或函数

CTRL+F10:运行到光标所在行

Disassembly:反编译

Alt+8跳转汇编,对应代码和汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdlib.h>
#include <stdio.h>
int main()
{
int x = 0;
int y = 0;
for(x = 0; x < 7; x++)
{
for(y = 0; y < 7; y++)
{
if(x + 3 == y || -x + 9 == y || x - 3 == y || -x + 4 == y)
{
printf("* ");
}
else
{
printf(" ");
}
}
printf("\r\n");
}
}

配调试界面(十六进制窗口、变量窗口、栈窗口、寄存器)

View -> Debug Windows ->Memory

View -> Debug Windows ->Watch

View -> Debug Windows ->Call Stack

View -> Debug Windows ->Register

3、循环&goto&基本代码

double 8字节、int 4字节,double类型数据按int输出会丢失字节,取值(ceil、floor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main()
{
int n = 15000;
double dblBitCount = 0.0;
for(int i = 1;i <= n; i++)
{
dblBitCount = dblBitCount + log10(i);
}
goto NEXT;
system("pause");
NEXT:
printf("%d\r\n",(int)ceil(dblBitCount));
return 0;
}

4、函数调用 (调用机制)

1,按调用约定传递参数。

1.1调用约定调用方(caller)需要和被调方(callee)作出以下约定:

  1. 参数的传递方向
  2. 参数的传输媒介
  3. 函数返回值的位置
  4. 释放参数空间的负责方,有且仅有一方去释放参数空间。

__cdecl:参数使用栈空间传递,从右往左函数返回值在寄存器,由调用方负责释放参数空间

__stdcall:参数使用栈空间传递,从右往左函数返回值在寄存器,由被调方负责释放参数空间

__fastcall:左数前两个参数使用寄存器传递,其它参数使用栈空间传递,从右往左函数返回值在寄存器,由被调方负责释放参数空间

2.保存返回地址,函数调用结束后应该执行的地址值

3.保存调用方的信息(栈底)

4.更新当前栈到栈顶(把当前栈顶作为被调方的栈底)

5.为局部变量申请空间。(抬高栈顶)

6.保存寄存器环境。(把即将使用的寄存器原值保存在栈里)

7.如果编译选项有/zI/Zi,则将局部变量初始化为oxcccccccc

8.执行函数体

9.恢复寄存器环境

10.释放局部变量的空间

11.恢复调用方的栈信息(栈底)

从右向左填充

对于栈来说,数据出入的口是栈顶。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int GetFib(int n,int m,int y)
{
int nF1 = 1;
int nF2 = 1;
int nFib = 0;
if (n == 2 || n == 1)
{
return 1;
}
return nFib;
}
int main()
{
unsigned int nFib1 = GetFib(1, 0, 3);
unsigned int nFib2 = GetFib(1, 0, 3);
for (int i = 2; i < 47; i++)
{
nFib1 = GetFib(1, 0, 3);
printf("%02d:%15u\t%f\r\n", i-2, nFib1, (double)nFib2 / nFib1);
nFib2 = nFib1;
}
system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.调用者在自己的栈帧里开辟好被调函数形参需要的空间

2.入栈 函数调用结束后应该执行的地址值,即返回地址,其实就是回收第一步为形参开辟的空间的指令的地址

3.进入被调函数了,入栈调用函数栈帧的栈底地址

4.在新函数的当前栈帧内为局部变量分配空间后,入栈局部变量

5.被调函数遇到return语句了,说明即将结束本函数了,就开始做回收本栈帧的空间的事了:

1)如果有返回值,那么把返回值赋值给EAX,如果没有则忽略这一步。

2)回收局部变量空间,即esp指向调用函数栈帧的栈顶了

3)提前存好的main函数栈帧的栈底地址赋值进入ebp寄存器,从而使得ebp指向main函数栈帧的栈底

4)把返回地址填入EIP寄存器,接着就会指向,回收main函数当初为被调函数开辟的两个形参的空间的指令地址

5)回收形参空间
————————————————
原文链接:https://blog.csdn.net/kangkanglhb88008/article/details/89739105

5、数组

1
2
3
4
5
6
7
int n = ...;
type ary[M] = ...;
ary[n] address:
(int)ary + sizeof(type)*n = 0x00400000

int *p = NULL;
printf("%p\r\n",&p[20]);

没有限制数组访问,默认可以任意访问

1
2
3
4
5
6
7
8
9
10
int main()
{
int arr = {1,2,3};
printf("%d-%p",arr[-1],&arr[-1]);
printf("%d-%p",arr[0],&arr[0]);
printf("%d-%p",arr[1],&arr[1]);
printf("%d-%p",arr[2],&arr[2]);
printf("%d-%p",arr[3],&arr[3]);
return 0;
}

利用越界,访问指定地址

1
2
3
4
5
6
7
8
9
10
0x0019ff2c
求出0x00400000

int main()
{
int n = 2;
printf("%p\r\n",ary[(0x00400000 - (int)ary) / sizeof(int)]);
system("pause");
return 0;
}

间接访问,[] * ->

1
2
3
4
5
6
7
8
9
10
11
12
13
void change(int n,int m)
{
int temp = n;
n = m;
m = temp;
}
int main()
{
int n = 1,m = 2;
change(n,m);
system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
void change(int arr[])
{
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
int main()
{
int arr[] = {1,2};
change(arr);
system("pause");
return 0;
}

内存结构示例图

两个BUG(Build-clean)

1
2
3
4
5
6
7
8
// 查看mainCRTStartup()源码
int main()
{
float f;
scanf("%f",&f);
system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
int foo()
{
goto NEXT;
return 0;
NEXT:
;
}
int main()
{
foo();
system("pause");
return 0;
}

6、二维数组

1
2
3
4
5
6
7
8
9
//多维数组是特殊的一维数组,它里面的元素是数组
//N维数组
ary[n] address:
(int)ary + sizeof(type)*n

type ary[N][M]
ary[x][y] address;
(int)ary + sizeof(type[M])*x // &ary[x]
+ sizeof(type)*y // &ary[x][y]
1
2
3
4
5
6
7
8
9
10
int ary[3][4] = {
{1,2,3,4},
{2,2,3,4},
{3,2,3,4}
};
int x = 2;
int y = 3;
printf("%p\r\n",&ary[x][y]);
printf("%p\r\n",&ary[x][4*x+y]);
printf("%p\r\n",(int)ary + sizeof(int[4])*x + sizeof(int)*y);

内存是一维存储,二维数组只有一个首地址,可以用一维数组访问到二维数组

1
2
3
4
5
6
7
8
type ary[N][M]
ary[x][y] address;
(int)ary + sizeof(type[M])*x + sizeof(type)*y
(int)ary + sizeof(type)*M*x + sizeof(type)*y
(int)ary + sizeof(type)*(M*x + y)

ary[3][4],从ary[0]开始访问
其实就是 4y+x

自定义strcpy实现对比

1
2
3
4
5
6
7
8
9
10
11
12
void mystrcpy(char szDst[],char szSrc[])
{
int i = 0
while (szSrc[i]!='\0') // 字符数组中'\0'表示结束符
{
szDst[i] = szSrc[i];
i++;
}
szDst[i]='\0';

// while(szDst[i++] = szSrc[i]);
}

7、 作用域

全局变量的初始化与否会影响编译文件的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int g_nTest = 0x87654093;
char g_szBuf[] = "Hello";
const char g_szTest[] = "jjyy";
int g_nTest2[0x10000]; //int g_nTest2[0x10000]={1};
void Fun(int n)
{
static int stc_nTest = 123;
stc_nTest++;
printf("%d\r\n",stc_nTest);
}
int main()
{
Fun(10);
Fun(20);
Fun(30);
return 0;
}

分析每种不同的变量,内存中定义

名称粉碎

汇编代码、内存、寄存器

变量放到寄存器,汇编里看不到??

0042开头,全局变量

19ff2c,栈地址

8、 地址和指针

*取值
&取地址

1
2
3
4
int a = 8;
int *pa = NULL;
pa = &a;
*pa = 999; //不能直接赋值指针

指针和数组的区别
数组在定义时已经明确类型,指针存放的是地址,还需要附带解释方式,即类型sizeof(type)

指针效率小于等于数组

1
2
3
float flt = 3.14f;
printf("%08x",*(int *)&flt);
printf("%08x",*(int)flt);

是否交换判定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void myswap(int *pn1,int *pn2)
{
int *nTmp = pn1;
pn1 = pn2;
pn2 = nTmp;
}
void myswap2(int *pn1,int *pn2)
{
int nTmp = *pn1;
*pn1 = pn2[0];
pn2[0] = nTmp;
}
int main()
{
int n1 = 8;
int n2 = 10;
myswap(&n1,&n2);
return 0;
}

字符串指针

1
2
3
4
5
6
7
8
char szTest[] = "Hello";
char *pszTest = "Hello";
szTest[0] = 'h';
pszTest[0] = 'h';
puts(szTest);
puts(pszTest);
getchar();
return 0;

修改常量区可读写;

打开二进制文件,查找rdata字符串,下两行的40,修改为C0

附带

局部变量区会变化,基本上默认C那些都是,之后就是三个寄存器。再之后才是函数传参

9、函数指针、数组指针

函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <string.h>
#include <stdlib.h>
char *foo(char szBuf[])
{
printf("%d\r\n",strlen(szBuf));
printf("%d\r\n",sizeof(szBuf));
//int ary[200];
char szBuf2[20];
strcpy(szBuf2,szBuf);
printf("%d\r\n",strlen(szBuf2));
printf("%d\r\n",sizeof(szBuf2));
//返回的是临时变量,可能被重新分配
return szBuf2;
}
int main()
{
char szBuf1[] = "Hello world!";
char* szBuf2 = "Hello world!";
printf("%d\r\n",strlen(szBuf1));
printf("%d\r\n",strlen(szBuf2));
printf("%d\r\n",sizeof(szBuf1));
printf("%d\r\n",sizeof(szBuf2));
printf("%d\r\n",sizeof("Hello world!"));

char *psz = foo(szBuf1);
puts(psz);
printf("%d\r\n",strlen(psz));
printf("%d\r\n",sizeof(psz));
system("pause");
return 0;
}

数组指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void SortA(int ary[], int nCount)
{
puts("冒泡法");
}
void SortB(int ary[], int nCount)
{
puts("选择法");
}

typedef void (_cdecl*PFNSORT)(int [], int);

int main()
{
int ary[54];
//函数的类型
//1、参数序列(包括参数个数,类型,顺序)
//2、调用约定
//3、返回值类型
PFNSORT pfn = NULL;
pfn = SortB;
//(*prnSort)(ary,54)
pfn(ary,54);

pfn = SortA;
pfn(ary,54);
return 0;
}

/*
意义:
1、函数指针,便于换
放在服务端,根据当前情况请求不同
*/

10、指针和多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
int main(int argc)
{
int ary[3][4] = {
{1, 2, 3, 4},
{10, 20, 30, 40},
{100, 200, 300, 400}
};

// 数组名是第0个元素的指针常量
// 二维数组的元素是一维数组
// int ary[3][4]的元素是 int[4]
// ary是int[4]类型的指针常量
printf("%p\r\n", ary);

// *ary得到int[4]一维数组
// *ary是int类型的指针常量
printf("%p\r\n", *ary);

// 任何类型的变量取地址得到该类型的指针
// &ary取地址得到int[3][4]类型的指针
printf("%p\r\n", &ary);

printf("%p\r\n", ary[0]);
printf("%p\r\n", ary[0][0]);

// ary是int[4]类型的指针常量
// ary + 1 <==> (int)ary + sizeof(int[4])*1
printf("%p\r\n", ary + 1);

// *ary是int类型的指针常量
// *ary + 1 <==> (int)*ary + sizeof(int)*1
printf("%p\r\n", *ary + 1);

// &ary取地址得到int[3][4]类型的指针
// &ary + 1 <==> (int)&ary + sizeof(int[3][4])*1
printf("%p\r\n", &ary + 1);

// 二维数组的元素是一维数组
// ary[0]是int[4]数组
// 数组名是第0个元素的指针常量
// ary[0]是int类型的指针常量
// ary[0] + 1 <==> (int)ary[0] + sizeof(int)*1
printf("%p\r\n", ary[0] + 1);

// 指针加整形得到同类型的指针常量
// ary是int[4]类型的指针常量
// ary+1得到int[4]类型的指针常量
// *(ary+1)得到一维数组int[4]
// 数组名是第8个元素的指针常量
// *(ary+1)是int类型的指针常量
// 对int*做[1]运算得到int
printf("%p\r\n", (*(ary + 1))[1]);

system("pause");
return 0;
}

/*
0019FF00
0019FF00
0019FF00
0019FF00
00000001
0019FF10
0019FF04
0019FF30
0019FF04
00000014
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main(int argc)
{
int ary[3][4] = {
{1, 2, 3, 4},
{10, 20, 30, 40},
{100, 200, 300, 400}
};
int (*p)[4] = ary;
int (*pAry)[3][4] = &ary;
printf("%p\r\n", p);
printf("%p\r\n", *p);
printf("%p\r\n", pAry);
printf("%p\r\n", *pAry);
printf("%p\r\n", **pAry);

printf("%p\r\n", p + 1);
printf("%p\r\n", *p + 1);
printf("%p\r\n", pAry + 1);
printf("%p\r\n", *pAry + 1);
printf("%p\r\n", **pAry + 1);
system("pause");
return 0;
}

/*
0019FF00
0019FF00
0019FF00
0019FF00
0019FF00
0019FF10
0019FF04
0019FF30
0019FF10
0019FF04
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, char **argv, char *envp[])
{
while(*envp != NULL)
{
puts(*envp);
envp++;
}
for(int i = 0; i < argc; i++)
{
puts(argv[i]);
}
char *aryPoint[3] = {
"Hello",
"world",
"C++"
};
return 0;
}

11、结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct tagStudent
{
char szName[5];
int nAge;
float fHeight;
double dblWeight;
unsigned short int wID;
char nGender;
};
int main(int argc, char **argv, char *envp[])
{
struct tagStudent stu = {
"Jack", //5个字节但是8位对齐所以补充到8?
25, //4个字节
185.0f, //4个字节
80.0, //8个字节
9527, //2个字节
'm' //1个字节
};
/*
char szName[5] = "Jack";
int nAge = 25;
float fHeight = 185.0f;
double dblWeight = 80.0;
unsigned short int wID = 9527;
char nGende = 'm';
printf("%d\r\n",sizeof(szName)); //5
printf("%d\r\n",sizeof(nAge)); //4
printf("%d\r\n",sizeof(fHeight)); //4
printf("%d\r\n",sizeof(dblWeight));//8
printf("%d\r\n",sizeof(wID)); //2
printf("%d\r\n",sizeof(nGende)); //1
*/
printf("%d\r\n",sizeof(stu)); //32
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct tagStudent
{
char szName[5];
char nGender;
int nAge;
double dblWeight;
float fHeight;
unsigned short int wID;
};
int main(int argc, char **argv, char *envp[])
{
struct tagStudent stu = {
"Jack",
'm',
25,
80.0,
185.0f,
9527
};
printf("%d\r\n",sizeof(stu));
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct tagStudent
{
char szName[5];
char nGender;
double dblWeight;
int nAge;
float fHeight;
unsigned short int wID;
};
int main(int argc, char **argv, char *envp[])
{
struct tagStudent stu = {
"Jack",
'm',
80.0,
25,
185.0f,
9527
};
printf("%d\r\n",sizeof(stu));
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct tagStudent
{
char szName[5];
char nGender;
int nAge;
double dblWeight;
unsigned short int wID;
float fHeight;
};
int main(int argc, char **argv, char *envp[])
{
struct tagStudent stu = {
"Jack",
'm',
25,
80.0,
9527,
185.0f,
};
printf("%d\r\n",sizeof(stu));
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
设编译对齐值为Zp
设结构体成员的地址和结构体首地址之差为offset
设结构体成员类型为member type
必须满足:
offset % min(Zp,sizeof(member type)) == 0

定义结构体自身的对齐值为 StructAlig
StructAlig = max(sizeof(member1 type),sizeof(member2 type),...,sizeof(memberEnd type))

设整个结构体的空间长度为 size
必须满足:
size % min(Zp,StructAlig) == 0
*/
struct tagStudent
{
char szName[5]; //+0 => 0%(min(8,sizeof(char)) => 0%(min(8,1)
int nAge; //+8 <= +6 => 6%(min(8,4))
double dblWeight; // +16
char nGender; // +24
float fHeight; // +28
unsigned short int wID; //+32,这里指的是偏移量,还要加上short int
}; // 34 % min(8,8) == 0 => 40
int main(int argc, char **argv, char *envp[])
{
struct tagStudent stu = {
"Jack",
25,
80.0,
'm',
185.0f,
9527
};
printf("%d\r\n",sizeof(stu));
return 0;
}

共用体(共用同一个内存结构,以最大成员字节开辟空间)

1
2
3
4
5
6
7
union unScore
{
char chLevel;
float fPoint;
char szTest[8];
}
// pScore->szTest = xxx => *(char *)&pScore->szTest

12、堆

栈分配和堆分配的区别?能否识别出内存中栈地址和堆地址

申请堆也是存储在栈,不过存储的是堆的地址,类似于常量地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stdafx.h"
#include <malloc.h>
#include <string.h>

int main(int argc, char **argv, char *envp[])
{
int *p = (int *)malloc(4);
*p = 999;

char *psz = (char *)malloc(20);
strcpy(psz, "Hello");

char *psz2 = (char *)realloc(p, 1);
psz2[0] = 'a';

p = (int *)realloc(psz2, 8);
p[0] = 0x6666;
p[1] = 0x8888;

free(p);
free(psz);
return 0;
}

堆结构分析:

堆的起始地址要减20字节,堆存储有附加数据(貌似是调试状态才有),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "stdafx.h"
#include <malloc.h>
#include <string.h>
#include <crtdbg.h>

#ifdef _DEBUG
#define malloc(n) _malloc_dbg(n, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int main(int argc, char **argv, char *envp[])
{
int *p = (int *)malloc(4);
*p = 999;
//free(p);

char *psz = (char *)malloc(20);
strcpy(psz, "Hello");

char *psz2 = (char *)realloc(p, 1);
psz2[0] = 'a';

p = (int *)realloc(psz2, 80);
p[0] = 0x6666;
p[1] = 0x8888;

free(p);
free(psz);
return 0;
}

只有调试版在释放堆空间时才填充数据,发布版是残留,发布版没有堆内没有调试信息

调试小技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "stdafx.h"
#include <malloc.h>
#include <string.h>
#include <crtdbg.h>

#ifdef _DEBUG
#define malloc(n) _malloc_dbg(n, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int main(int argc, char **argv, char *envp[])
{
char *psz = (char *)malloc(strlen("Hello world!"))
puts("test");
puts("test");
puts("test");
puts("test");
puts("test");
strpcy(psz, "Hello world!");
puts("test");
puts("test");
puts("test");
puts("test");
puts("test");
free(psz);
system("pause");
return 0;
}

位运算(针对二进制的运算,运算前要转换成十进制):

是一种底层运算,加减本质上都是位运算

1
2
3
4
5
6
7
8
9
10
11
12
13
位是一种二进制运算
A and 1 = A
A or 0 = A
A and 0 = 0
A or 1 = 1
A xor A = 0
A xor 0 = A
A xor 1 = not A
A and not A = 0
A or not A = 1

此处的1默认为 1111
假设A为1010

有符号数移位补符号位,无符号位移位补0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int myabs(int n)
{
unsigned int i = 7; // 无符号位 0111
i = i >> 1; // 3
i = -1; // 1001
i = i >> 1; // 0xffffffff >> 1 = 0x7fffffff

int j = 7;
j = j >> 1; //3
i = -1;
i = i >> 1; // 0xffffffff >> 1 = 0xfffffffff

return 0;
}
int main(int argc, char **argv, char *envp[])
{
int n = myabs(-5);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
int myabs(int n)
{
//具体数学
//-5 = 1101 => 1010+1 = 1011 = 00 00 00 03 右移31位,补符号位1,第一位1,左移补31位1
int i = n >> 31;//if n >= 0, i=0; else i = 0xffffffff = -1
n = n ^ i;//if i = 0, n = n; else i = 0xffffffff, n = ~n
return n - i;//if i = 0, n - i = n; else i = -1, n - i = n + 1
}
int main(int argc, char **argv, char *envp[])
{
int n = myabs(-5);
return 0;
}

位运算2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define ADD 1
#define DEL 2
#define EDT 4
#define QUE 8
int main(int argc, char **argv, char *envp[])
{
int nPrivilege = 0;
nPrivilege = nPrivilege | DEL; //二进制运算,或0010
nPrivilege = nPrivilege | EDT; //二进制运算,或0100

if (nPrivilege & ADD)
{
puts("add");
}
if (nPrivilege & DEL)
{
puts("DEL");
}
if (nPrivilege & EDT)
{
puts("EDT");
}
if (nPrivilege & QUE)
{
puts("QUE");
}
return 0;
}

位运算3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct tagPrivilege
{
int add:1;//分配1位
int del:1;
int edt:1;
int que:1;
};
int main(int argc, char **argv, char *envp[])
{
struct tagPrivilege pri = {0};
pri.del = 1;
pri.edt = 1;
int n = pri.del; // n = -1 内存存的是补码,默认int有4位,此处被限制为1变成符号位
/*
unsigned int edt:1;
n = pri.edt; // n = 1
*/
if (pri.add)
{
puts("add");
}
if (pri.del)
{
puts("DEL");
}
if (pri.edt)
{
puts("EDT");
}
if (pri.que)
{
puts("QUE");
}
return 0;
}

位运算4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//xxxxxxxxxxxxxxxxxxxxxx qq eeee ddd a
struct tagPrivilege
{
int add:1;//分配1位
int del:3;
int : 0;
//xxxxxxxxxxxxxxxxxxxxxxxxxx qq eeee
//xxxxxxxxxxxxxxxxxxxxxxxxxxxx ddd a
unsigned int edt:4;
int que:2;
};
int main(int argc, char **argv, char *envp[])
{
struct tagPrivilege pri = {0};
pri.del = 9; // pri.del = 1 001B = 1 超长度,看第n位就行
pri.edt = 20; // pri.edt = 1 0100b = 4
int n = pri.del; // n = -1 内存存的是补码,默认int有4位,此处被限制为1变成符号位
/*
unsigned int edt:1;
n = pri.edt; // n = 1
*/
if (pri.add)
{
puts("add");
}
if (pri.del)
{
puts("DEL");
}
if (pri.edt)
{
puts("EDT");
}
if (pri.que)
{
puts("QUE");
}
return 0;
}

https://www.yuque.com/books/share/08f3006b-131e-4f17-85cb-ab78f322c090