苏州网络科技公司建网站,百度下载安装免费版,中山网站建设文化渠道,wordpress 攻击文章目录 1.内存和地址1.1理解内存地址酒店大堂#xff1a;内存的入口房间号#xff1a;内存地址的意义酒店的楼层划分#xff1a;内存的结构酒店的房间单位#xff1a;计算机中的常见单位 1.2如何理解编址 2.指针变量和地址2.1取地址操作符#xff08;)2.2 指针变量… 文章目录 1.内存和地址1.1理解内存地址酒店大堂内存的入口房间号内存地址的意义酒店的楼层划分内存的结构酒店的房间单位计算机中的常见单位 1.2如何理解编址 2.指针变量和地址2.1取地址操作符)2.2 指针变量和解引用操作符*)以及如何 拆解指针类型2.3使用指针的好处2.4指针变量的大小为什么指针变量的大小固定 3.指针变量类型的意义指针变量的类型指针的解引用数据类型大小的差异示例代码 3.2 指针与整数的加减操作加法操作减法操作背后的原理使用场景 3.3void*指针 4. const修饰指针4.1const的使用4.2.const修饰指针的应用场景 5.指针运算5.1 指针与整数相加减5.2指针之间的减法运算5.3指针的关系运算 6.野指针6.1 野指针的成因未初始化指针指针指向已释放的内存指针越界 6.2 野指针的危害6.3 规避野指针的策略及时初始化指针确保指针指向有效内存及时释放指针指向的内存避免指针越界 7. assert断言7.1什么是断言7.2 assert宏的用法 7.3 assert的实践指南7.4 示例代码 8.指针的使用和传址调用和传值调用8.1strlen 函数的模拟实现8.2传值调用和传址调用的示例小结 1.内存和地址 1.1理解内存地址
酒店大堂内存的入口
当你走进一家酒店时大堂是你首先到达的地方正如CPU访问内存时它通过内存地址找到数据的存放位置。每个内存地址都像是房间的门牌号告诉CPU它需要的数据住在哪个“房间”里。
房间号内存地址的意义
在这家酒店内存中每个房间内存单元都有一个独一无二的号码地址。这些号码不仅帮助客人CPU找到他们的房间数据还揭示了酒店内存的组织结构。与酒店的房间号码一样内存地址是顺序排列的这让寻找连续的数据块变得容易。
酒店的楼层划分内存的结构
酒店楼层被划分成了不同的区域每个区域代表了内存中的一个特定部分。这种划分有助于提高酒店运营的效率同样内存划分也旨在优化计算机资源的使用。
前台区域寄存器这是最快速访问的区域用于处理客人数据的即时请求。在内存中这相当于CPU寄存器提供了最快的数据访问速度。VIP区缓存位于前台附近的是VIP区访问速度快于普通房间用于存放短期内频繁访问的贵宾客人数据。这对应于计算机的缓存它存储了近期将被频繁使用的数据以减少访问主内存的时间。客房区主内存酒店的主体部分为大多数客人提供住宿服务。在计算机中这相当于主内存存储了当前正在使用的程序和数据。地下室硬盘/二级存储虽然访问速度不如楼上的区域地下室用于存放不常用但仍需要保留的物品。这在计算机中对应于硬盘或其他形式的二级存储用于长期存储数据。
酒店的房间单位计算机中的常见单位
在管理这家酒店时了解不同房间的大小和类型是必要的。类似地在计算机中我们也有多种单位来衡量内存和数据的大小
字节Byte最基本的存储单位好比酒店中的一个储物柜足以存放少量物品数据。千字节KBKilobyte约等于1000字节相当于一个小衣橱可以存储更多的物品。兆字节MBMegabyte约等于1000千字节像是一个小房间用于存放更大量的物品。吉字节GBGigabyte约等于1000兆字节等同于一间大客房能存储大量数据。太字节TBTerabyte约等于1000吉字节好比酒店中的一整层楼为存储巨量的数据提供空间。 内存单元的编号 地址 指针
1.2如何理解编址 在这里硬件与硬件之间的协同合作是通过“线”来连接的这里是被叫做地址总线。那么什么是地址总线呢 简单来说地址总线可以理解为一系列用于传递电脉冲信号的线路。在地址总线上每一根线路都可以承载一个电脉冲信号这个信号通常是二进制的也就是说它可以是有电表示为1或者无电表示为0。CPU通过这些有电和无电的信号组合来指定内存中的具体地址。
例如假设有一个4位的地址总线那么它可以有(2^4 16)种不同的信号组合从0000到1111分别可以用来指定内存中的16个不同的位置。CPU通过在这四条线路上发送特定的有电1和无电0的组合来告诉内存它想要访问的确切位置。每一个含义都是一个地址。 地址信息被下达给内存在内存上就可以找到与该地址相对应的数据将数据通过数据总线转存到CPU寄存器。
理解了变量在内存中是如何存储的。现在我们可以知道每个变量都有一个内存地址这个地址可以通过使用运算符来获取。
#include stdio.hint main(){int var 5;printf(变量的地址%p\n, var);return 0;
}2.指针变量和地址 2.1取地址操作符)
取地址操作符是一个一元操作符用于获取变量在内存中的地址。当你在一个变量前使用时它返回该变量的内存地址。例如
#include stdio.hint main()
{int var 5;printf(%d\n, var);printf(变量的地址%p\n, var);return 0;
}先使用的是比较小的地址
2.2 指针变量和解引用操作符*)以及如何 拆解指针类型 指针变量是存储另一个变量地址的变量。它不同于一般的变量指针变量指向的是位置而不是值。声明一个指针变量时你需要在其类型前加上*符号。例如int* ptr;声明了一个指向整型的指针变量ptr。
指针变量用于存储变量的地址如下所示
#include stdio.hint main()
{int var 5;int *ptr var;printf(指针ptr存储的地址%p\n, ptr);printf(通过ptr访问var的值%d\n, *ptr); // *ptr是解引用操作得到存储在ptr地址的整型数据return 0;
}2.3使用指针的好处
使用指针的原因可以通过一个现实生活中的类比来更好地理解 假设你有一本非常重要的书你想与多个朋友分享。你有两个选项一是给每个朋友复制一本书相当于在程序中复制数据这样做非常耗费资源纸张、墨水等二是告诉朋友们这本书在你家的确切位置相当于使用指针让他们自己来查阅。
以下是使用指针的几个主要优点 节省资源与复制完整数据相比共享数据的位置即地址可以显著减少内存使用。在上述类比中复制书籍消耗的资源要远多于仅仅共享书籍的位置。 提高效率当需要在程序的不同部分或者不同函数间共享大量数据时使用指针可以直接访问数据而不是花时间和资源去复制数据。就像你告诉朋友们书的位置他们可以直接访问而不需要等待你复制并递送一本书给他们。 允许修改原数据通过指针函数可以直接修改原始数据的值而不仅仅是它的副本。这就像你允许朋友在你的书上做标记所有查阅这本书的人都能看到这些更改而不是每个人都在不同的副本上做标记这些更改互不影响。 支持动态数据结构指针是实现诸如链表、树、图等动态数据结构的关键。这些结构在运行时可以扩展和缩减需要指针来指向和链接其各个部分。就像在一个巨大的图书馆里书籍可能不会按顺序排列但你可以通过一个系统指针来找到每本书的确切位置。 实现复杂的数据结构在C语言中指针是实现数组、字符串和其他复杂数据结构的基础。通过指针可以灵活地操作这些结构中的元素就像你可以通过书架的索引系统来快速找到任何一本书一样滴。
所以容易看出指针是程序设计中的一种强大工具它通过提供对内存直接访问的能力使得资源管理更高效数据处理更灵活。理解和掌握指针就像学会在图书馆中熟练地找到任何一本书是提高编程技能的重要一步
2.4指针变量的大小
在讨论指针变量的大小时我们实际上是在讨论指针变量本身占用的内存空间大小。这与指针指向的数据的大小无关而是指存储内存地址所需要的空间大小。指针变量的大小取决于计算机的架构32位或64位和操作系统。
为什么指针变量的大小固定
无论指针指向的是一个整数、一个字符还是一个复杂的结构体指针本身的大小都是固定的。这是因为指针变量只存储内存地址而内存地址的大小是由计算机的架构决定的。 32位系统中的指针在32位系统中地址空间是基于32位地址的这意味着系统可以寻址(2^{32})个独立的内存地址。因此在32位系统中指针变量通常占用4字节32位的内存空间。 64位系统中的指针相比之下64位系统可以寻址(2^{64})个独立的内存地址这大大增加了可用的内存空间。因此在64位系统中指针变量的大小通常是8字节64位。
3.指针变量类型的意义 指针变量的类型
虽然指针大小和指针类型无关指针类型定义了指针指向的数据类型。但这对于解引用操作和指针算术运算来说非常重要因为它决定了指针操作时应该考虑的内存大小。
int* ptr; // 指向整型的指针
char* cptr; // 指向字符的指针每种类型的指针都有其对应的内存地址大小但指向的数据类型决定了解引用时的行为。
指针的解引用
解引用是通过指针访问其指向地址中存储的数据的操作。使用解引用操作符*来获取指针指向的值。
char*和int*是C语言中两种不同类型的指针它们的主要区别在于所指向的数据类型及通过这些指针进行操作时内存中读取或修改数据的方式。char*是指向字符数据的指针而int*是指向整型数据的指针。这一差异影响了指针运算和解引用操作的行为。
数据类型大小的差异
在大多数平台上char类型占用1字节而int类型通常占用4字节这可能根据不同的系统或编译器有所不同。这意味着当你通过这些指针进行加减运算时指针的移动步长也会有所不同。
示例代码
让我们通过一个例子来演示这两种指针类型的区别
#include stdio.h
int main()
{int var 0x11223344;int* ptr var;*ptr 0;return 0;
}#include stdio.hint main()
{int var 0x11223344;char * ptr var;*ptr 0;return 0;
}容易看出int*能访问四个字节,而char解引用只能访问一个字节
3.2 指针与整数的加减操作
指针加上或减去一个整数会导致指针向前或向后移动一定数量的内存位置。这里的“一定数量”取决于指针指向的数据类型的大小。
加法操作
当我们对指针执行加法操作时我们实际上是在移动指针。例如如果我们有一个指向int类型的指针每个int占用4个字节这可能因编译器和平台而异那么当我们对这个指针加2时实际上是让指针向前移动了8个字节指向了第三个整数。
#include stdio.hint main(){int arr[] {10, 20, 30, 40, 50};int* ptr arr; // 指向数组的第一个元素ptr 2; // 移动指针到第三个元素printf(当前指针指向的值为: %d\n, *ptr); // 输出30return 0;
}减法操作
类似地对指针执行减法操作会使指针向后移动。使用同样的int指针例子如果我们从指向第三个整数的指针中减去1那么指针会移动回第二个整数的位置。
include stdio.hint main()
{int arr[] {10, 20, 30, 40, 50};int* ptr arr 2; // 初始时指向数组的第三个元素ptr - 1; // 移动指针回到第二个元素printf(当前指针指向的值为: %d\n, *ptr); // 输出20return 0;
}指针的类型就是决定向前或向后一步能走多远的距离
背后的原理
指针加减整数的操作背后的原理基于内存地址的算术运算。当我们对指针加上或减去一个整数时编译器会将这个整数乘以指针指向的数据类型的大小以字节为单位然后将结果加到或从指针的当前值上。这就是为什么指针的移动会依赖于它所指向的数据类型的大小。
使用场景
指针与整数的加减操作在多种场景下非常有用例如遍历数组、动态内存管理等。
3.3void*指针
void指针可以理解为无具体类型指针泛型指针可以接收任意类型地址但是不能进行指针的±整数和解引用的运算 在上面代码中int类型的变量地址赋给char*类型的指针变量会有如此警告 下面我们使用void*指针接收地址 容易看出void*可以接收不同类型的地址但无法进行运算 一般是使用在函数参数的部分用来接收不同类型数据的地址达到泛型编程使一个函数处理多种数据
在C语言中const修饰符用于声明常量而当const与指针结合使用时产生了一些独特的语义和行为。本文将深入探讨const修饰指针的技术性并提供详细的代码示例帮助读者更好地理解其应用与原理。
4. const修饰指针 4.1const的使用
const修饰指针的语法形式为const int *ptr;或int *const ptr;。 第一种形式表示指针指向的数据是常量第二种形式表示指针本身是常量。
指向常量的指针指针指向的数据是常量不能通过指针修改该数据。 指向内容不可修改指向对象可以修改
const int num 10;
const int *ptr num;
//*ptr 20; // 错误无法修改指针所指向的数据
//ptr another_num//正确可以修改指针指向常量指针指针本身是常量不能修改指针的指向。
int num 10;
int *const ptr num;
//*ptr 20; // 正确可以修改指针所指向的数据
//ptr another_num; // 错误无法修改指针本身的指向4.2.const修饰指针的应用场景
函数参数中的常量指针用于传递不希望修改的数据同时保证函数内部不会修改该数据。
void print_data(const int *ptr)
{printf(Value: %d\n, *ptr);
}int main()
{int num 10;print_data(num);return 0;
}常量指针与字符串常量常量指针经常用于指向字符串常量以保护字符串数据不被修改。
const char *str Hello, world!;
// *str h; // 错误无法修改指向的字符串数据在c艹中const修饰的彻底是常量
5.指针运算 5.1 指针与整数相加减
指针与整数相加减是指针运算中最基本的操作之一它允许程序员在内存中进行灵活的定位和偏移。
#include stdio.hint main(){int arr[] {10, 20, 30, 40, 50};int *ptr arr; // 指向数组的第一个元素// 指针加整数实现指针偏移ptr ptr 2; // ptr现在指向数组的第三个元素printf(Value: %d\n, *ptr); // 输出30// 指针减整数实现指针偏移ptr ptr - 1; // ptr现在指向数组的第二个元素printf(Value: %d\n, *ptr); // 输出20return 0;
}在这个示例中指针 ptr 指向了一个整型数组 arr 的第一个元素。通过将指针与整数相加减可以实现对指针的偏移从而实现对数组中不同位置的访问。 上面已经讲过了这里只是再提一下
5.2指针之间的减法运算
指针之间的减法运算可以得到它们之间的距离以元素个数为单位这对于计算数组中元素的个数或者实现迭代器等功能十分有用。
#include stdio.hint main()
{int arr[] {10, 20, 30, 40, 50};int *ptr1 arr[0]; // 指向数组的第一个元素int *ptr2 arr[3]; // 指向数组的第四个元素// 指针相减得到指针之间的距离以元素个数为单位int distance ptr2 - ptr1;printf(Distance: %d\n, distance); // 输出3即ptr2与ptr1之间相隔3个元素return 0;
}在这个示例中通过指针 ptr1 和 ptr2 的减法运算得到它们之间相隔的元素个数即数组中的距离。
5.3指针的关系运算
指针之间还可以进行关系运算包括相等性比较和大小比较。
#include stdio.hint main() {int arr[] {10, 20, 30, 40, 50};int *ptr1 arr[1];int *ptr2 arr[3];// 指针相等性比较if (ptr1 ptr2) {printf(Pointers are equal.\n);} else {printf(Pointers are not equal.\n);}// 指针大小比较if (ptr1 ptr2) {printf(ptr1 is less than ptr2.\n);}else{printf(ptr1 is not less than ptr2.\n);}return 0;
}在这个示例中指针 ptr1 和 ptr2 分别指向数组中的不同元素。通过关系运算符可以判断指针之间的相等性和大小关系。
6.野指针 6.1 野指针的成因
未初始化指针
int *ptr;
printf(%d, *ptr); // 未初始化的指针ptr被解引用指向未知内存地址指针指向已释放的内存
#include stdio.h
#include stdlib.hint main()
{int *ptr (int *)malloc(sizeof(int)); // 分配内存空间if (ptr ! NULL) {*ptr 10; // 向分配的内存空间写入数据printf(Value: %d\n, *ptr); // 输出数据free(ptr); // 释放内存空间ptr NULL; // 将指针置为NULL防止成为野指针printf(Value: %d\n, *ptr); // 试图访问已经释放的内存空间}return 0;
}在这个例子中指针 ptr 在使用 malloc() 分配了内存空间后被用来存储一个整数值并通过 printf() 函数输出了该值。然后通过 free() 函数释放了 ptr 所指向的内存空间并将 ptr 设置为 NULL。最后尝试再次使用 printf() 输出 ptr 所指向的值此时 ptr 已经成为了野指针因为它指向的内存空间已经被释放这可能导致程序出现不可预测的行为。
指针越界
int *get_pointer()
{int num 10;int *ptr num;return ptr; // 返回了一个局部变量的指针
}int main(){int *ptr get_pointer();printf(%d, *ptr); // ptr指向的内存超出了作用域成为了野指针return 0;
}6.2 野指针的危害
野指针可能导致程序崩溃或产生不可预测的行为。在调试过程中野指针可能会给程序员带来困惑和耗费大量的时间来定位问题的根源。野指针可能会导致内存泄漏或内存损坏从而影响程序的性能和稳定性。
6.3 规避野指针的策略
及时初始化指针
int *ptr NULL; // 显式初始化指针为空指针确保指针指向有效内存
int *ptr (int *)malloc(sizeof(int));
if (ptr ! NULL)
{// 分配内存成功后再进行操作
}及时释放指针指向的内存
int *ptr (int *)malloc(sizeof(int));
if (ptr ! NULL)
{free(ptr);ptr NULL; // 释放内存后将指针置为空指针
}避免指针越界
int *get_pointer()
{int num 10;int *ptr num;return ptr; // 返回局部变量的指针会导致野指针
}7. assert断言 assert宏的用法、原理和实践指南
7.1什么是断言
在C语言中断言是一种在程序中加入的检查点用于检查程序的假设是否成立。 如果假设不成立则断言将导致程序终止并输出相关的错误信息帮助程序员定位问题所在。
7.2 assert宏的用法
assert宏定义在assert.h头文件中其基本语法如下
#include assert.hassert(expression);其中expression是一个表达式如果表达式的值为假即0则assert宏将终止程序的执行并输出相应的错误信息。
7.3 assert的实践指南 合理选择断言的位置将断言放置在程序中的关键位置如函数入口、循环体内部、关键计算或数据操作之前。 明确表达断言的意图断言的表达式应该简洁明了能够清晰地表达程序的假设或条件。 避免副作用在断言中避免引入副作用以免影响程序的正常运行。 编译时开启断言在开发和测试阶段建议将断言开启以便及时发现和解决问题。 谨慎使用断言断言应该用于检测程序的不变条件和可预期的错误情况而不是用于处理运行时错误或非预期的情况。
7.4 示例代码
#include stdio.h
#include assert.hint main()
{int x 10;int y 20;// 检查假设x 应该小于 yassert(x y);printf(Assertion passed: x is less than y\n);return 0;
}在这个示例中断言检查了表达式 x y 是否成立如果不成立则程序终止并输出相应的错误信息否则输出“Assertion passed: x is less than y”。 如果不需要了在#include assert.h加上desine NDBUG,编译器就会禁用所有assert()语句。
8.指针的使用和传址调用和传值调用 8.1strlen 函数的模拟实现
strlen 函数用于计算字符串的长度它计算的是/0之前的长度并返回字符串的长度。以下是 strlen 函数的简化模拟实现
#include stdio.h// 模拟实现 strlen 函数
size_t myStrlen(const char *str)
{const char *ptr str;while (*ptr ! \0) {ptr;}return ptr - str;
}int main() {char str[] Hello, world!;size_t len myStrlen(str);printf(Length of %s is %zu\n, str, len);return 0;
}在这个示例中myStrlen 函数通过遍历字符串直到遇到终止符 \0 来计算字符串的长度并返回结果。
8.2传值调用和传址调用的示例 很容易看出x和ay和b的地址不一样xy是独立的空间在swap1里面交换x和y。不会影响a和bswap1在使用的时候是直接吧变量本身传给了函数这就是传值调用。 形参是实参的一份临时拷贝对形参修改不影响实参。 swap1是失败的 传址调用可以让函数和主调函数之间建立真正的联系在函数内部修改主调函数的变量
小结
所以未来函数中只是需要主调函数的变量来实现计算就可以传值若要修改主调函数中的变量的值就需要传址调用。 ——end…