呼伦贝尔市建设局网站域名推荐
并非从0开始的c++之旅 day2
- 一、变量
- 1、 变量名的本质
- 二、程序的内存分区模型
- 1、内存分区
- 运行之前
- 运行之后
- 三、栈区注意事项
- 四、堆区
- 1、堆区使用
- 2、堆区注意事项
- 五、全局变量静态变量
- 1、静态变量
- 2、全局变量
- 六、常量
- 1、全局const常量
- 2、局部const常量
- 七、字符串常量
一、变量
既能读又能写的内存对象,成为变量;
若一旦初始化后不能修改的对象则称为常量。
1、 变量名的本质
变量名的本质:一段连续内存空间的别名
程序通过变量来申请和命名内存空间 int a = 0;
通过变量名访问内存空间
不是向变量名读写数据,而是向变量所代表的内存空间中读写数据;
变量修改方式:直接修改、间接修改
void test01() {int a = 10;///1、直接修改a = 20;//2、间接修改int* p = &a;//*p 解引用*p = 200;printf("a = %d\n", a);
}```自定义数据类型练习```c
struct Person {char a;int b;char c;int d;
};void test02()
{struct Person p1 = { 'a',10,'b',20 };//直接修改d属性p1.d = 100;printf("%d\n", p1.d);//间接修改d属性struct Person* p = &p1;p->d = 200;printf("%d\n", p1.d);
}
这种间接修改太简单了,我们还要学一种
若使指针p加一,p指针会跳过一个结构体的字节数
其中char类型占1个字节,int类型占4个字节,a占用0~3,b从4开始,系统用内存对齐的方式让a也占用了虽然他不使用但是在b之前的字节
结果使用以下代码验证
printf("%d\n", p);
printf("%d\n", p+1);
可得两值差16,同理我们可以用这种方式修改变量
char* p = &p1;printf("%d\n", *(int*)(p + 12));printf("%d\n", *(int*)((int*)p + 3));
将p指针类型改为char *型,这时增加p指针的值会使其会一个一个往前走,在输出时,因为我们需要的是4个字节的int类型的内容,故需要做强制类型转换,将其转换为int *类型
同理如上面的第三行代码
二、程序的内存分区模型
1、内存分区
运行之前
可以简单先分为代码区和数据区
程序执行过程:
1)预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:将目标文件链接为可执行程序
当我们编译完成生成可执行文件之后,我们可以通过linux下size命令可以查看一个可执行文件基本情况:
执行size命令后,系统会给出一下几个数据:
text 代码区
data 静态数据/全局初始化数据区
bss 未初始化初始化数据区
dec 文件十进制总和
hex 文件十六进制总和
filename 文件名
通过以上数据可知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好了3段信息,分别为代码区、数据区和未初始化数据区3个部分(有些人直接把data和bss合起来叫做静态区或全局区)
- 代码区:存放CPU执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了他的指令。另外,代码区还规划了局部变量的相关信息
- 全局初始化数据区\静态数据区:该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和t)和常量数据(如字符串常量)
- 未初始化数据区(又叫bss区):存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为0或空(NULL)
运行之后
相比运行之前,多出了栈区和堆区
- 栈区(stack):栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间
- 堆区(heap):堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
栈区:先进后出,编译器管理数据开辟,释放,容量有限,不要将大量数据开辟到栈区
堆区:容量远远大于栈区,程序员手动开辟数据,手动释放数据
三、栈区注意事项
int* fun() {int a = 10;//栈上创建的变量return &a;
}void test01() {int* p = fun();//结果依然不重要,因为上面的a早已被释放,再去操作这块数据属于非法操作printf("%d\n", *p);printf("%d\n", *p);
}
栈上的数据在执行完方法就释放了,第一次printf能打出正确结果其实是因为编译器认为你可能会处理错了数据,故保留一次,第二次打印时结果才是正常的,已经是一个随机数了。
char* GetString() {char str[] = "hello world";return str;
}void test02() {char* p = NULL;p = GetString();printf("p = %s\n", p);
}
上述代码中,将p打印后的结果为字符型乱码,因为str是指向字符串的指针,但字符串在函数运行结束后自动释放了,故他指向的地址已经为乱码,打印出来也为乱码
这里的hello world原本在常量区,无法修改,被复制了一份到栈区
两段代码告诉我们,栈上的数据出了函数体后就不要再使用了 ,也即不要返回局部变量的地址,局部变量在函数体执行完毕后会被释放,再次操作就是非法操作,结果未知!
四、堆区
1、堆区使用
int* getSpace() {int * p = malloc(sizeof(int) * 5);if (p == NULL)return NULL;for (int i = 0; i < 5; i++) {p[i] = 100 + i;}return p;
}void test01() {int* p = getSpace();for (int i = 0; i < 5; i++)printf("%d\n", p[i]);//释放数据free(p);p = NULL;
}
使用malloc创建的空间在堆区,printf多少次结果都一样,创建后如果没有使用就是垃圾,需要free
防止free后的指针仍指向原来那块已经被释放的空间,称为野指针,我们需要把它设成NULL
2、堆区注意事项
void allocateSpace(char* p) {char* temp = malloc(100);memset(temp, 0, 100);strcpy(temp, "hello world");p = temp;}void test02() {char* p = NULL;allocateSpace(p);printf("p = %s\n", p);
}
打印出来的结果为NULL,因为test中的p和函数的参数p属于同级指针,test的p为NULL时,同级指针无法改变他的值,若要改需要更高级的二级指针。
修改为以下代码
void allocateSpace2(char** p) {char* temp = malloc(100);memset(temp, 0, 100);strcpy(temp, "hello world");*p = temp;}void test03() {char* p2 = NULL;allocateSpace2(&p2);printf("p2 = %s\n", p2);
}
p一开始为NULL,参数p的值为传入p的地址,temp为堆区开辟的地址,解参数p后赋值temp,也就是将temp所含的地址赋予p,这样p就能直接指向temp所指向的堆
如果给主调函数中,一个空指针分配内存,在被调函数中利用同级指针是分配失败的
解决方式:利用高级指针修饰低级指针
五、全局变量静态变量
1、静态变量
生命周期在程序运行结束时死亡
在程序运行前就分配内存
默认属于内部链接属性,在当前文件中使用
static int a = 10;//全局作用域void test01() {static int b = 19;//局部作用域
}
默认内部链接属性,在文件外是访问不到的,如在同一个项目的另一个类中就访问不到这里的a
2、全局变量
默认在c语言下,全局变量前加了关键字 extern
属于外部链接属性
extern int g_b;g_b = 100;
若在其他类中写了一个全局变量,想在main函数中引用,需要有第一行这个代码
这个代码就是告诉编译器,其他文件中有这么一个变量,链接时要去其他文件中寻找
但是要是找不到这个全局变量,就会报错,报错为1个无法解析的外部命令,该报错是在链接阶段报的
六、常量
1、全局const常量
//const 修饰的常量
const int a = 10;//全局const常量void test01() {//直接修改失败//a = 100;//间接修改int* p = &a;*p = 100;printf("%d\n", a);
}
如果直接修改,则a会有红线,显示语法错误
如果间接修改,vs2019会报错,显示写入访问权限冲突
全局const常量放在常量区中,受到常量区的保护
2、局部const常量
void test02() {const int b = 10;//直接修改失败//b = 100;//间接修改int* p = &a;*p = 100;printf("%d\n", b);
}
直接修改依然语法不行
间接修改正常运行,因为局部常量放在栈区,我们称之为伪常量
伪常量不可以初始化数组
七、字符串常量
void test03() {char* p1 = "hello world";char* p2 = "hello world";char* p3 = "hello world";printf("%d\n", p1);printf("%d\n", p2);printf("%d\n", p3);printf("%d\n", &"hello world");
}
由上述代码结果可知,用字符串指针指向字符串常量,得到的地址都是一样的,可知字符串常量是可以共享的
void test04() {char* p1 = "hello world";p1[0] = "w";printf("%d\n", p1);
}
运行后会报错,字符串常量是不能修改的,虽然ANSIC中规定是不定义的,但vs是不能修改的
vs会把多个相同的字符串常量看成一个