指针篇章-(1)

 指针(1)学习流程

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

内存和地址

内存

所谓的指针就是为了管理内存空间,划分的一个内存一个单元。

内存单元是计算机内存中的最小存储单位,通常以字节(Byte)为单位。每个内存单元都有一个独特的地址,以便CPU可以准确地找到并读写数据。
指针在C语言或其他允许指针操作的编程语言中非常重要,因为它们提供了直接访问和操作内存的能力。使用指针,程序可以避免数据复制,从而提高效率,尤其是在处理大型数据结构(如数组和复杂的数据结构)时。

地址 

内存单元的编号就是地址 就是指针

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行数据传递。
但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来
而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址(就如同宿舍很多,需要给宿舍编号样)。
计算机中的编址,并不是把每个字节的地址记录下来而是通过硬件设计完成的。
钢琴、吉他 上面没有写上“剁、来、咪、发、唆、拉、西”这样的信息,但演奏者照样能够准确找到每一个琴弦的每一个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且所有的演奏者都知道。本质是一种约定出来的共识!
硬件编址也是如此
我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每一种含义都代表一个地址。
地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。

cpu内部寄存器地址线和数据线的布局。具体来说,在cpu的左边有8个地址线,从0到7,用于指定内存中的地址。每个地址线对应一个数据线,用于读写内存的数据。此外,还有一个控制总线(R/W),用于控制数据的读写操作。在cpu的右边是内核(Kernel)部分,它与地址线和数据线相连,用于接收来自内存的数据或指令。

通过物理线传到某一个过程 访问那个数据的内存

这里也有物理地址和虚拟地址

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

指针变量和地址

 指针变量是什么

1.指针是内存单元中一个最小的单元编号,也就是地址

2.平时口语说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针就是地址,口语中说的指针通常指的是指针变量,是用来存放内存地址的变量。

 地址大小决定指针变量的大小

这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以.
一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址

总结:
指针变量是用来存放地址的,地址是唯一标示一个内存单元的,指针的大小在32位平台是4个字节,在64位平台是8个字节。 

指针变量的举例

这里我们需要知道创建了变量的本质是申请一块空间

当然这里可以看见,在系统申请空间的时候,是随机给一个地址的

也就是

在C语言中,变量的内存分配通常是在程序的堆栈(stack)或堆(heap)上进行的。堆栈通常用于存储局部变量,而堆则用于存储动态分配的内存。当程序执行到声明变量的语句时,系统会为该变量分配一块内存空间,并将其地址赋给变量。
关于地址的选择,现代操作系统和编译器通常会使用一种称为“地址空间布局随机化”(Address Space Layout Randomization, ASLR)的技术来提高安全性。ASLR会确保每个进程的内存地址空间是随机的,这样就增加了攻击者利用内存漏洞的难度。因此,每次程序运行时,即使它申请的内存空间大小相同,分配的内存地址也可能不同。

这里引入

&单目操作符(取地址操作符)

#define _CRT_SECURE_NO_WARNINGS 1
int main()
{
	int a = 100;
	int arr[10];
	return 0;
}

 %p打印地址

&单目操作符(取地址操作符)

取地址 取出来的是较小的地址 当知道较小的地址后 顺藤摸瓜就找到了

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

 此时p也就是一个指针变量

当然 此时的语法还不对 但是此时取出的a的地址 给到了p 此时p也就是我们口中说的指针变量

这里平时说的p是指针 其实说的p是指针变量

取出来的地址 放到p里面 所以p是指针 也就是指针变量

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int a = 100;
	int *p = &a;

	//int arr[10];
	//printf("%p\n", &a);
	return 0;
}

p是指向a的

因为p可以找到a

所以p是指向a的

*说明是指针变量 int 说明p指向的对象是int类型的

指针变量就是存放地址所以引用指针变量

任何东西只要放到指针里面 就呗指针认为是指针变量

这里不仅仅只是为了存放地址

此时p可以很好的找到a的地址

此时*p叫解引用操作符 或者间接访问操作符

这里需要知道

对p解引用 找到的结果找到的是a

取出a的地址 解引用 然后此时a就等于赋值的数值

关于指针变量大小

等同于找老莫我想吃鱼一样 老大不方便动手 小弟动手

地址是由地址线传入 一个地址线产生一个二进制位数 32个地址线产生2的32次方位地址线

所以在平台上面 x86的环境下面

一个指针的大小需要四个字节

所以地址长度决定指针变量的长度

所以64位就是一个指针就是8个字节

举例 32位的情况下 都是四个字节 和类型无关

这里插入两个内容 关于环境和计算 有利于指针的学习

计算机基础( 计算机里面为什么是32位指的是什么)(C语言代码举例)(画图超详细解析)_c语言中32位是什么意思-CSDN博客

计算机基础知识讲解(原码反码补码)(以及在C语言里面是如何计算和运用的)-CSDN博客 

这里需要知道x86 是32位的环境

至于是为什么

"x86"这个名称来源于Intel在1978年推出的8086处理器,它是第一个真正意义上的16位微处理器。后来,Intel和AMD等公司推出了基于8086架构的扩展和改进型号,如80286、80386和80486等。这些处理器虽然技术上仍然是16位的,但它们引入了扩展寄存器和指令,使得它们能够处理32位数据。
由于这些处理器在市场上获得了巨大成功,人们开始将它们统称为"x86"架构。这个名称并没有明确指出它是16位还是32位,但实际上,从80386开始,"x86"架构就已经支持32位操作了。
因此,"x86"这个名称并不是指32位环境,而是指一系列基于8086架构的处理器。不过,由于这些处理器在32位时代获得了广泛应用,人们往往用"x86"来指代32位环境。这种用法虽然不太准确,但在实际应用中却是普遍接受的。

回归正题

 x64 就是八个字节

这里需要知道

指针里面存方的一般是16进制

至于为什么是16进制 往往是因为16进制计算范围较大 比较方便

但是需要知道的是

存放的二进制 显示的16进制 简单说就是方便

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

指针类型

没有解引用之前 a的地址确实给到pa

但是当解引用的时候只改变了第一个字节

说明 指针类型决定指针的权限

整形指针一次只能访问四个字节

这就是指针类型的意义

如果你想访问一个字节 char

如果是四个字节 用int

再多就是float等等

因为这里你需要知道,在初始化的时候,分配内存和数值是随机分配的,你进行指针的访问,也只是根据类型来进行访问

指针类型的意义

 指针类型决定了指针在进行解引用操作符的时候访问几个字节,也就是决定指针的权限

加上数字 发现不一样 int*和char*每次跳过的也不一样

指针类型的计算

指针类型决定+1 -1的时候一次走的距离

指针类型决定向前或者向后走一步 多少距离

 简单的说就就是,此时打印出来的地址,减去之前的地址是大四个字节的

从而引出指针类型的意义

根据上述,我们反过来讲解指针类型的意义

从而引出指针类型的意义

此时是短整型char 前后差距是一个字节 

但是是int的情况下 前后差距就是四个字节

解引用不同的类型之间转化

对于指针解引用不同的类型访问不同的字节长度

取出的类型是一个整形的类型

但是放到了短整型里面

此时就会导致数据的丢失

void可以存放任何类型的地址 因为没有具体类型

在C或C++等编程语言中,
`void` 指针是一种特殊的指针类型,它不代表任何具体的内存地址类型。
`void` 指针可以用来存放任何数据类型的地址,因为它是所有类型的基类型。
但是,需要注意的是,虽然 `void` 指针可以存储任何类型的地址,
但是不能直接使用它来访问或操作这些地址所指向的数据,因为编译器不知道具体的数据类型,也就无法进行正确的类型匹配和内存布局。
在实际应用中,如果想要通过 `void` 指针访问具体类型的数据,需要进行类型转换,即将 `void` 指针强制转换为相应的类型指针。
例如:
```c
int a = 10;
void* void_ptr = &a;
int* int_ptr = (int*)void_ptr;
*int_ptr = 20; // 现在 a 的值变为 20
```
在这个例子中,`void_ptr` 存储了 `int` 类型变量 `a` 的地址,
但是不能直接通过 `void_ptr` 来修改 `a` 的值。我们通过类型转换得到了 `int` 指针 `int_ptr`,
然后就可以通过 `int_ptr` 来修改 `a` 的值了。

使用 `void` 指针时需要格外小心,
因为错误的类型转换会导致未定义行为,比如内存访问越界,这可能会导致程序崩溃或产生不可预测的结果。
因此,在使用 `void` 指针时,确保进行正确的类型转换和内存管理是非常重要的。

所以在不确定是什么类型的时候 用void类型的接受

不过需要知道的是

void具有局限性 因为没有具体类型

所以无法运算访问字节

如图

这里显示的是未知的大小

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

const

首先解释一下const

 在C语言中,'const'用于声明一个只读的变量。这意味着该变量一旦被初始化,其值就不能被改变。常量通常用于表示某些固定的、不变的值,例如物理地址、尺寸或数学常数等。

在C语言中,`const` 是一个关键字,用于修饰变量,以表明它们的值在定义之后不能被改变。`const` 关键字具有以下两个主要属性:
1. 只读属性:被 `const` 修饰的变量在初始化之后,其值不能被修改。这意味着一旦你给一个 `const` 变量赋了一个值,你就不能再次给它赋一个新值。
2. 类型安全:`const` 关键字告诉编译器该变量是一个常量,因此在程序执行期间,试图修改这样一个变量的值会导致编译错误或运行时错误(取决于如何尝试修改它)。
`const` 可以用在变量声明的任何位置,具有不同的含义:
- `const` 在变量类型之前:表示变量是一个只读的常量,不能被赋予初始值,必须在声明时初始化,并且它的值在程序的整个运行期间都不可更改。
- `const` 在变量类型之后:表示变量本身是可变的,但其指向的数据是只读的。这在指针中非常有用,可以确保指针指向的数据不被修改,但指针本身(即内存地址)可以改变。
例如:
```c
const int a = 10; // 声明一个常整型变量,初始化为10,之后不能更改
int const b = 20; // 同上,这两种声明方式等效
int arr[10];
const int n = 5; // 声明一个常整型变量,表示数组的大小
int* ptr = arr;
const int* const ptr_to_const = arr; // 声明一个指向常量的常量指针
// ptr_to_const指向的arr数组元素是常量,不能被修改,同时ptr_to_const本身的值也不能被改变
```
使用 `const` 可以提高代码的可读性和可维护性,同时也帮助编译器进行更好的优化。此外,`const` 关键字在C++中还有更多的用途,例如用来定义常量对象和成员函数,但在C语言中,它的使用主要局限于变量声明。

简单的说 ,可以片面的理解为,就是他存在的意义就是,防止新手修改老手的代码的时候,不小心吧东西修改错误

const 本质

下面我会介绍什么时候回进行修改 以及范围 当然在这之前先介绍完const本身的属性

const 本质 是不能修改 

此时用const修饰a之后 

这里的a具有了长属性 长属性就是不能被修改的属性

这里需要知道

虽然a不能被修改,但是本质还是变量 就是常变量

但是在c和c++里面const是不一样d

在c里面 const修饰的是变量

在c++里面则是常量

所以写C语言的话 后缀就要写c 

但是这样是不符合规则的

为什么是不合规的,前面已经说明 ,这里再详细的说一下

`const int a = 10;` 声明了一个常量 `a`,它的值不能被改变。接着,`int* p = &a;` 声明了一个指向 `a` 的指针 `p`。
然后,`*p = 0;` 这行代码试图修改通过指针 `p` 所指向的内存位置的值。由于 `a` 是一个 `const` 变量,它的值不能被改变,所以这行代码是不正确的,会导致编译错误。
在 C 语言中,`const` 关键字的作用是确保变量在其生命周期内不被修改。尝试修改一个 `const` 变量的值会导致编译错误。因此,如果您尝试执行 `*p = 0;`,编译器会报错,因为它不允许您通过指针 `p` 来修改 `a` 的值。
正确的做法是只读取 `a` 的值,而不是尝试修改它。例如:
```c
const int a = 10;
int* p = &a;
// 正确的使用方式是读取a的值,而不是修改它
printf("%d\n", *p); // 输出 10
```
如果想要修改 `a` 的值,需要首先取消 `const` 限制,或者使用另一个变量来存储 `a` 的值,然后修改那个变量的值。例如:
```c
int a = 10;
int* p = &a;
*p = 0; // 正确,修改了a的值
```
或者:
```c
const int a = 10;
int b = a; // b 是一个普通变量,可以被修改
b = 0; // 正确,修改了b的值,而不是a
```

const的使用规则

 所以此时p指向的是a的地址

所以这个时候可以理解为 p本身就有一个地址 然后p指向了一个地址 p里面存放了a的地址

所以

p指向一个地址

p本身有一个地址

p存放一个地址

数值出来是100

const修饰指针变量的时候放到*的右边 限制的是指针变量本身,指针变量不能指向其他变量 但是可以修改指针变量指向的内容

p不能修改 但是*p指向的内容可以进行修改

const放到*左边

修饰指针变量的时候

此时限制的是*p

但是可以修改指针变量本身

也就是此时 指针指向的内容是不能发生变化的 但是指针(也就是指针变量本身是可以发生变化的)

const生动举例

当const在*左边的时候、,,const* ,,这里假设有一对男女朋友,这个有一天女孩说,我想吃雪糕需要五十块 ,这个男孩说那我没钱一共十块钱,买不起,那女孩就不高兴了,说:那你的钱(本身的变量)不发生变化的话,那我只能变化,换男朋友了。

当const在*右边的时候、,,*const ,,这里假设还是这一对男女朋友,还是这个场景,这个有一天女孩说,我想吃雪糕需要五十块 ,这个男孩说那我没钱一共十块钱,但是没事我借钱40,(男孩这个指针指向的变量发生了变化),买了一个雪糕还有0元,那女孩就继续跟着你了,也就是指针变量本身没有发生变化

 

const在*左边指针变量本身的地址变化 但是指向的内容不变化

所以这里就是你不给我花钱 指针女孩换男朋友m->n 女孩本身变化 男朋友的钱m数值不变化

此时m愿意花钱 然后等于把const放到右边 等于m愿意花钱 女孩指针p本身不变化 不换男朋友,但是男朋友m的钱发生变化 也就是指针变量本身不发生变化,但是指向的变量可以发生变化

当然两边也都可以加上const数值

总结

想搞定变量*const int

想搞定指针变量本身 const * int

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

assert断言

这里先简单讲一下assert的断言,下面会有assert断言和野指针的结合使用 

测试和写代码直接转换问题 

这里链接一下 relase和debue的区别关于Visual Studio Installer总是出现(LNK1168无法打开 )(不需要关机直接处理好)_vs installer打不开-CSDN博客

测试和写代码直接转换问题

这里讲一下为什么在这个测试和写代码直接转换的时候会不一样

C语言在测试和写代码时可能会出现不一样的结果,这主要是由于以下几个原因:
1. **浮点数精度问题**:C语言中的浮点数计算通常不会得到精确的结果,因为计算机内部用二进制表示浮点数时,无法精确表示所有的小数。所以在不同平台或者不同编译器上运行时,浮点数的运算结果可能会有细微的差异。
2. **Endianness(字节序)**:字节序是指计算机中字节存储的顺序。C语言中没有明确指定字节序,这可能导致在不同硬件平台上,多字节数据(如整数、浮点数)的存储顺序不同,从而影响数据的解析和运算。
3. **编译器和运行时的优化**:编译器在将C代码编译成机器码时,可能会进行各种优化。这些优化可能会改变代码的执行逻辑或者运算结果。另外,运行时的库函数也可能对输入数据进行不同的处理。
4. **测试环境和写代码的环境差异**:如果在不同的环境(如不同的操作系统、硬件平台、编译器版本等)中进行测试和编写代码,可能会因为环境的差异导致结果不同。
5. **代码的实现细节**:在编写代码时,可能会因为实现的细节不同(例如,算法效率、边界条件处理等)而在测试时得到不同的结果。
为了减少这些差异,通常需要在代码中加入适当的注释,说明可能存在的差异,并在文档中详细描述测试环境。此外,也可以使用标准库函数,如`<stdint.h>`中定义的固定宽度整数类型,来避免因为浮点数精度和字节序引起的问题。在编写和测试代码时,应尽可能在相同的环境中进行,以确保结果的一致性。

assert 存在的意义

 assert可以确保传过来的指针不是空指针

`assert`是一个在C语言中广泛使用的宏,它的存在主要有以下意义:
1. **调试辅助**:在开发过程中,`assert`用于在运行时检查程序的假设是否成立。如果`assert`语句的条件评估为假,则程序会抛出一个断言失败异常,通常会导致程序终止并输出错误信息,这样可以帮助开发者快速定位问题所在。
2. **静态代码分析**:在编译时,`assert`可以作为静态代码分析的工具。通过检查`assert`语句,编译器可以确保程序在运行之前满足一定的条件。这有助于在编译阶段发现潜在的错误,而不是在运行时才暴露出来。
3. **文档和代码的同步**:`assert`的使用可以在一定程度上作为代码注释,它表明了程序员对程序某一部分的预期。这样,其他阅读代码的人可以更清楚地理解代码的假设和约束。
4. **性能分析**:在某些情况下,`assert`可以用来测试代码路径是否应该被执行。如果`assert`失败,这意味着程序处于一个不应该到达的状态,这可能有助于发现性能瓶颈或逻辑错误。
5. **代码简洁性**:使用`assert`可以避免在代码中充斥着大量的错误检查和异常处理代码,使得正常逻辑更加清晰。
6. **沙盒环境的创建**:在某些安全敏感的应用中,`assert`可以用来创建沙盒环境,确保程序在特定的安全边界内运行。
总之,`assert`是C语言中一个非常有用的工具,它有助于提高代码的质量和开发效率,同时也使得代码更加健壮。然而,需要注意的是,`assert`不应该用于修复错误,而应该仅用于检测错误。在生产环境中,应该确保`assert`语句在编译时被正确处理,或者在运行时不会对程序的稳定性产生影响。

assert具体举例 

需要包含头文件assert.h 

这个代码的健壮性更好

限制了所改变的内容

 ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

strlen

strlen存在的意义

`strlen`是C语言标准库函数之一,它的作用是计算字符串的长度,不包括终止符`\0`(空字符)。`strlen`函数在`string.h`或`stdlib.h`头文件中定义。
`strlen`的意义和用途包括:
1. **获取字符串长度**:`strlen`函数可以方便地获取一个字符串的长度,这对于许多字符串操作(如字符串比较、复制、连接等)是非常必要的。
2. **内存分配**:在动态分配内存时,通常需要知道字符串的长度,以便为字符串加上结束符`\0`并分配足够的空间。`strlen`可以提供这个长度信息。
3. **字符串处理**:在进行字符串处理时,如搜索、替换、截断等操作,需要知道字符串的实际长度。`strlen`函数提供了这个信息。
4. **安全性**:在使用字符串时,`strlen`可以帮助避免缓冲区溢出等安全问题。例如,在复制字符串时,知道目标缓冲区的大小和源字符串的长度可以确保不会超出缓冲区的边界。
5. **跨平台兼容性**:`strlen`是C语言标准库的一部分,因此它在不同的操作系统和编译器之间具有很好的兼容性。
使用`strlen`时需要注意的是,由于`strlen`不考虑字符串中的宽字符或多字节字符,它只计算单字节字符的长度,因此在处理非ASCII字符串时可能不够准确。对于宽字符或多字节字符串的长度计算,应使用相应的库函数,如`wcslen`(宽字符串长度)或`strlen`的变体,这些函数能够正确处理多字节字符。 

具体的举例 

这里是和assert进行了一个结合 

利用assert代码加入到代码 里面会减少出错性(这里涉及野指针的,下面会讲到) 

包含头文件

string.h或stdlib.h头文件中定义

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

指针的基本算法 

指针的作用就是访问内存

指针的加法运算 

指针的运算和访问内存息息相关下面会讲到 

 

这里是整形,一个整形是四个字节,在指针的类型里面已经讲到 

当然不能搞混

所以逻辑就是

代码举例

指针版本1

下标方式访问

指针方式访问

指针加减 整数

解引用

每次指针+1

每次加一之后则变化指向的数值

如图 

指针版本2

这个运行的前提条件是数组在内存里面是连续存放的

指针的减法运算(指针-指针)

指针的运算

指针-指针

指针-指针绝对值

得到是两个指针之间的元素个数

没有意义

不是所有的计算都是有意义的

而指针的计算应该是有意义的

指针-指针的运算

低地址和高地址的区别

指针-指针限制条件必须指向同一空间

所以计算出的结果是空间的长度 

 

但是像这个计算出的结果 ,因为不是一个空间所以计算出的结果不是中间的长度,而是没有意义的运算 

指针里面strlen的运算

统计的是\0之前出现的个数

包含头文件

strlen头文件string

数组名相当于首元素地址

字符的地址用字符指针接收

strlen 指针的运算方式1

这里是数组名每次进行++ 然后最后求出总的字符的长度 

strlen指针的运算方式2

这个采取的是指针-指针的运算方式

也就是同一空间 start=str 这里的str是首元素地址 也就是指向的是首元素

然后每次进行str++再减去首元素地址 也就是指针-指针的运算

因为

这里str最后指向的是str这个空间的最后一个元素

start指向的是身str的首元素地址 也就是这个空间的第一个元素

指针的关系大小的运算

区分 

运算的代码 

这里是首元素的地址 

 

 sz是长度,p<arr+sz意思就是循环条件就是小于首元素地址加上整体长度

此时是不存在越界访问的情况的因为是小于而不是是小于等于

然后打印*p

每次P++

从而实现代码

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

野指针以及越界访问的问题

野指针的定义 

野指针(Wild Pointer)是指向未知或者不可访问内存地址的指针。在C或C++编程语言中,野指针通常是由于指针没有被正确初始化而产生的。这意味着指针不指向任何有效的内存地址,而是可能指向堆、栈或者代码区的任意位置。
定义:
野指针是一种不安全指针,它的值没有指向程序中已知地址的任何有效内存。这种指针的值可能是任意地址,也可能是一个随机的旧值。在野指针上进行解引用操作(即通过指针访问它所指向的内存)会导致未定义行为(Undefined Behavior),这通常会导致程序崩溃或者产生错误的结果。
野指针的常见来源包括:
1. 指针声明后没有立即初始化。
2. 指针释放后没有重新初始化。
3. 指针通过局部变量返回函数外,而该局部变量已经超出作用域。
4. 在动态内存分配失败时,忽略错误并将指针置为`NULL`以外的值。
为了避免野指针,程序员应该遵循以下最佳实践:
- 总是初始化指针,使其指向一个有效的内存地址,或者明确地将其设置为`NULL`。
- 在使用指针之前,检查它是否为`NULL`。
- 避免指针的悬挂(dangling pointer)现象,即指针指向的内存已经被释放。
- 使用现代C++特性,如智能指针,可以自动管理内存,减少野指针的风险。

指针正确代码和错误代码的对比

正确代码 

错误代码

也就是烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫

这里为什么会产生烫这个字和函数栈有关,因为函数栈溢出的时候就会产生烫这个字

烫烫烫的问题

这里为什么会产生烫这个字和函数栈有关,因为函数栈溢出的时候就会产生烫这个字

在C语言编程中,栈溢出(stack overflow)是一个常见的问题,它发生在程序的栈空间被耗尽的时候。栈是用来存储局部变量和函数调用的上下文信息(如返回地址、函数参数等)的区域。当一个函数调用自身或其他函数时,这些信息会被推入栈中。如果函数调用层次太深或者递归调用的深度超过了栈的大小限制,就会导致栈溢出。
栈溢出时,程序会出现未定义行为(undefined behavior),这可能导致程序崩溃、数据损坏或其他不可预测的结果。在中文里,并没有“烫”这个字与栈溢出直接相关的说法。如果您在某个特定的上下文或方言中遇到了“烫”这个词与编程错误或异常相关联,那可能是一个特殊的用法或者是某个特定社群的俚语,而不是一个标准的计算机科学术语。
为了避免栈溢出,程序员应该:
1. 限制递归的深度,确保递归调用的层级不会超过栈的最大容量。
2. 使用尾递归优化(tail recursion optimization),如果编译器或语言支持,这可以避免栈空间的增加。
3. 避免在栈上分配大量内存,特别是在递归函数中。
4. 使用堆(heap)来存储较大的数据结构,而不是栈,因为堆的容量远大于栈。
如果您在某个特定的文档、教程或者社区中看到了“烫”这个词与栈溢出相关的解释,那么它可能是一个特定的术语或者是一个翻译错误。在这种情况下,建议您查找更多的信息或者向该社区的成员澄清这个问题。

野指针造成的问题

野指针容易造成的问题

 野指针(Wild Pointer)是指向未知或者不可访问内存地址的指针。在C或C++编程中,野指针通常是由于指针没有被正确初始化而产生的。使用野指针进行解引用(dereferencing)会导致未定义行为(Undefined Behavior),这可能导致程序崩溃、数据损坏或其他不可预测的结果。
野指针造成的问题主要包括以下几点:
1. **程序崩溃**:野指针可能指向内存中的任何位置,包括操作系统或其他程序的内存区域。解引用野指针可能会读取或修改这些位置的内容,导致程序崩溃。
2. **数据损坏**:野指针可能导致对程序其他部分数据的意外修改,这种修改可能是不可逆的,从而导致程序行为变得不可预测。
3. **安全问题**:野指针可能被用于发起缓冲区溢出攻击(Buffer Overflow Attack)。攻击者可以利用野指针向程序中写入恶意数据,从而覆盖返回地址或其他关键数据,导致程序执行意外的代码。
4. **性能下降**:野指针可能导致程序在运行时产生意外的分支,从而影响程序的性能。
5. **调试困难**:由于野指针导致的问题通常是未定义行为,因此在出现问题时,调试和定位错误可能会非常困难。
为了避免野指针造成的问题,程序员应该遵循以下最佳实践:
- 总是初始化指针,使其指向一个有效的内存地址,或者明确地将其设置为`NULL`。
- 在使用指针之前,检查它是否为`NULL`。
- 避免指针的悬挂(dangling pointer)现象,即指针指向的内存已经被释放。
- 使用现代C++特性,如智能指针,可以自动管理内存,减少野指针的风险。
正确管理指针是确保软件质量和稳定性的重要方面,也是避免野指针问题的关键。

野指针的非法访问问题

形成非法访问

可以看到每次的数值是随机的

野指针的越界访问问题

这里是10个元素循环11次

循环第十一次的时候就已经造成越界访问

所以此时的指针就变成了野指针

指针指向的空间释放

越界和传址问题图解 

 

野指针的寻址的问题

但是遗憾的是a的空间运行结束进行释放 a的空间还给操作系统了 但是下面的空间还是进行寻址 也就是此时找的是空地址 此时这个指针就是野指针 此时野指针再访问空间 就是非法访问

这里不能指向这个空间 ,因为这里创建的空间最后会进行销毁

也就是说如果指向这个空间的haul,最后指向的是一个空的地址

最后导致野指针的发生

简单的举例就是,小敏今天开了一个房间是901,准备进行诈骗。然后让小帅第二天来,小帅第一天吧钱交出去之后,第二天来到这个酒店,说我要进去901,但是酒店说了,这个人退房了,已经没有这个房间了。

这个就是指针的寻址问题,

因为在另外一个函数里面进行运算的事情,只能返回一个数值,而不是整个地址,这个函数需要进行运算的时候,或者需要进行诈骗的时候会开辟一块空间,不需要进行诈骗会吧这个空间关闭。但是如果你还是需要回去那个空间,自然就找不到这个空间了。

越界和传址问题图解 

如何避免野指针

本来就是拴着的狗 你还去里面 肯定咬你

避免指针越界访问

更加安全

assert断言和野指针的使用

包含头文件 assert.h

也就是assert进行断言 如果p不等于空指针 则没事

如果等于空指针 则 断言失败 报错

直接报错了 如果不满足条件

这里是满足条件才继续往下走

断言成功 继续运行

断言只要是表达式就可以 不一定非得说是指针

禁用很简单

assert对程序员很友好

所以在写代码的时候适当assert一下 对你是刮目相看的

release是发布版本 发布出去 还断言干什么

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

传值调用 传址调用

两者区别

首先就是顾名思义

传值调用传过去的是数值

传址调用传过去的是地址

在C语言中,函数对参数的传递有两种方式:传值调用(Pass by Value)和传址调用(Pass by Reference)。
传值调用:
在传值调用中,函数接收的是实际参数值的一个副本。
这意味着在函数内部对参数的任何操作都不会影响原始的变量。
传值调用适用于基本数据类型(如int、float等),因为这些数据类型可以轻松地通过值传递来复制。
传址调用:
在传址调用中,函数接收的是实际参数地址(即变量的内存地址)。
这意味着在函数内部对参数的任何操作都会直接影响原始的变量。
传址调用通常用于指针类型或数组,因为这些数据类型无法简单地通过值传递来复制,而是需要传递它们的地址。
简单来说,传值调用是传递数据的副本,而传址调用是传递数据所在内存的地址。下面是一个示例,展示两种调用方式的差异:

#include <stdio.h>

// 传值调用示例
void modifyValue(int value) {
    value = 50;
}

// 传址调用示例
void modifyReference(int *ptr) {
    *ptr = 50;
}

int main() {
    int a = 25;

    // 使用传值调用
    modifyValue(a);
    printf("After modifyValue: a = %d\n", a); // a 仍为 25

    // 使用传址调用
    modifyReference(&a);
    printf("After modifyReference: a = %d\n", a); // a 变为 50

    return 0;
}

在上述代码中,通过传值调用修改的变量 a 的值没有被改变,因为传递的是 a 的副本。而通过传址调用修改的是变量 a 的值,因为传递的是 a 的地址。

两者的情况分析

不是所有情况都适合

xy都有自己的空间

所以实际是xy进行交换半天 但是ab没有进行交换

虽然形参得到了实参的数值 但是实参有自己的空间 对形参的修改不会影响实参

所以没有达到最终的效果

怎么解决

所以此时就是间接找到了ab的地址

因为此时指针指向的是地址 

此时是直接找到地址

画图解释‘

如果不指向地址的情况下 形参是开辟自己的空间 但是指向的是ab的地址从而对ab的地址进行操作

所以最终还是三个变量之间的交换

相当于就是直接对a b 操作

传址调用

总结

也就是主要是函数内部可以操作外面 就需要传址调用

如果只是接收数值 需要传值调用

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

补充

指针类型和C语言编程语言环境的区别

指针类型是C语言编程中的一个核心概念,而C语言编程语言环境则是指C语言本身及其相关的编译器、运行时库、操作系统等软件和硬件环境的总和。下面分别解释这两者的区别:
### 指针类型
指针类型是指在C语言中用于存储和管理内存地址的数据类型。指针本身是一个变量,其值是另一个变量的内存地址。通过指针,可以间接访问和操作内存中的数据。
指针类型包括以下几个方面:
1. **基本指针类型**:如`int *p;`中的`p`是一个指向整型的指针。
2. **空指针**:在C中,`NULL`是一个定义在`stddef.h`中的空指针常量,表示没有指向任何有效内存地址的指针。
3. **指向指针的指针**:如`int **pp;`中的`pp`是一个指向整型指针的指针。
4. **函数指针**:指向函数的指针,可以用来调用函数,如`void (*fp)(int);`。
5. **数组指针**:指针可以指向数组,如`int (*arr)[10];`中的`arr`是一个指向含有10个整数的数组的指针。
指针类型在C语言中的使用非常广泛,因为它们提供了内存操作的高效途径,并且是实现数据结构(如链表、树、图等)的基础。
### C语言编程语言环境
C语言编程语言环境包括了C语言本身以及使用C语言进行编程时所依赖的所有外部资源。这些资源可以包括:
1. **编译器**:将C源代码转换为机器代码的工具,如GCC、Clang等。
2. **运行时库**:在程序运行时提供额外功能的库,如标准库`libc`,它包含了像`printf`、`malloc`这样的函数。
3. **操作系统**:提供程序执行和管理硬件资源的平台,如Linux、Windows等。
4. **开发工具**:包括编辑器、调试器、性能分析工具等,如Vim、Emacs、GDB等。
5. **硬件平台**:指针类型和C语言的其他特性在不同的硬件平台上可能有所不同,因为硬件架构会影响内存管理方式和指针的实现。
总结来说,指针类型是C语言中的一个语言特性,而C语言编程语言环境则是一个更为广泛的概念,包括了C语言本身以及用于编写和执行C程序的所有工具和平台。

指针类型+1增加四个字节和编程环境+1增加四个字节的区别

在C语言中,"指针类型+1增加四个字节"和"编程环境+1增加四个字节"这两者之间的区别在于它们所指的具体内容和上下文。
### 指针类型+1增加四个字节
1. **指针自增操作**:当你对一个指针进行`++`操作时,例如`int *p; p++;`,如果`int`类型占用四个字节,那么指针`p`的内容将增加四个字节,指向下一个`int`类型的位置。这是指针操作的一种,是指针本身值的改变。
2. **指针的移动**:这里的"增加四个字节"是指指针的数值增加了`sizeof(int)`的大小。`sizeof(int)`在大多数现代平台上的确是四个字节,但这并不是固定的,它取决于编译器和操作系统。
### 编程环境+1增加四个字节
1. **数据大小的变化**:在编程环境的上下文中,"+1增加四个字节"可能指的是在某个上下文中,数据类型的大小增加了。例如,如果编程环境从32位升级到64位,那么通常整数类型的大小会从四个字节增加到八个字节。
2. **性能优化或需求**:在某些情况下,编程环境的更新可能不会改变数据类型的大小,但可能会引入新的数据类型或者优化,以满足更高的性能需求或者更好的兼容性。
3. **内存管理的变化**:在更广泛的上下文中,"增加四个字节"也可能指的是内存管理策略的变化,比如操作系统或编译器可能会在指针周围增加额外的元数据,以支持更高级的内存分配或优化技术。
总的来说,"指针类型+1增加四个字节"是指针操作的直接结果,而"编程环境+1增加四个字节"是指整个编程语言和运行时环境的改变,这些改变可能影响数据类型的大小、内存布局、性能特性等多个方面。这两者是不同层次的概念,一个是语言层面的操作,另一个是整个环境层面的变化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/421329.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QML中表格中数据获取

1.在生成的动态表格中获取某格数据的内容 import QtQuick 2.15 import QtQuick.Window 2.15import QtQuick.Controls 2.0 import Qt.labs.qmlmodels 1.0 import QtQuick.Layouts 1.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")TableMod…

探索Redis 6.0的新特性

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存中数据结构存储系统&#xff0c;通常被用作缓存、消息队列和实时数据处理等场景。它的简单性、高性能以及丰富的数据结构支持使其成为了众多开发者和企业的首选。在Redis 6.0版本中&#xff0c;引入了一…

ubuntu22.04 成功编译llvm和clang 3.4.0,及 bitcode 函数名示例,备忘

1, 获取llvm 仓库 从github上获取&#xff1a; $ git clone --recursive https://github.com/llvm/llvm-project.git 2, 检出 llvmorg-3.4.0 tag 针对llvm 3.4.0版本&#xff0c;检出 $ cd llvm-project $ git tag $ git checkout llvmorg-3.4.0 3, 配置并编译llvm 使用 M…

矩阵爆破逆向之条件断点的妙用

不知道你是否使用过IDA的条件断点呢&#xff1f;在IDA进阶使用中&#xff0c;它的很多功能都有大作用&#xff0c;比如&#xff1a;ida-trace来跟踪调用流程。同时IDA的断点功能也十分强大&#xff0c;配合IDA-python的输出语句能够大杀特杀&#xff01; 那么本文就介绍一下这…

Siemens-NXUG二次开发-获取prt中体与类型、实体面与类型、实体边与类型、边上点的Tag标识[Python UF][20240302]

Siemens-NXUG二次开发-获取prt中体与类型、实体面与类型、实体边与类型、边上点的Tag标识[Python UF][20240302] 1.python uf函数1.1 NXOpen.UF.Obj.CycleObjsInPart1.2 NXOpen.UF.Obj.AskTypeAndSubtype1.3 NXOpen.UF.Modeling.AskBodyFaces1.4 NXOpen.UF.Modeling.AskFaceEdg…

韦东山嵌入式Liunx入门驱动开发四

文章目录 一、异常与中断的概念及处理流程1-1 中断的引入1-2 栈(1) CPU实现a ab的过程(2) 进程与线程 1-3 Linux系统对中断处理的演进1-4 Linux 中断系统中的重要数据结构(1) irq_desc 结构体(2) irqaction 结构体(3) irq_data 结构体(4) irq_domain 结构体(5) irq_domain 结构…

mac苹果电脑c盘满了如何清理内存?2024最新操作教程分享

苹果电脑用户经常会遇到麻烦:内置存储器(即C盘)空间不断缩小&#xff0c;电脑运行缓慢。在这种情况下&#xff0c;苹果电脑c盘满了怎么清理&#xff1f;如何有效清理和优化存储空间&#xff0c;提高计算机性能&#xff1f;成了一个重要的问题。今天&#xff0c;我想给大家详细介…

Unity 切换场景

场景切换前必须要将场景拖动到Build中 同步加载场景 using System.Collections; using System.Collections.Generic; //using UnityEditor.SearchService; using UnityEngine; // 场景管理 需要导入该类 using UnityEngine.SceneManagement;public class c3 : MonoBehaviour {…

XUbuntu22.04之如何找到.so库所在的软件包?(二百一十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

如何添加极狐GitLab Runner 信任域名证书

本文作者 徐晓伟 极狐Gitlab Runner 信任实例域名证书&#xff0c;用于注册注册极狐 GitLab Runner。 问题 参见 极狐gitlab-runner-host.md 说明 解决方案是使用颁发给域名 gitlab.test.helm.xuxiaowei.cn 的证书&#xff0c;可以使用自己的域名去各大云厂商免费申请&#…

Linux系统中的高级多线程编程技术

在Linux系统中&#xff0c;多线程编程是一种常见的并发编程模型&#xff0c;通过利用多线程可以实现程序的并发执行&#xff0c;提高系统的性能和响应速度。在Linux系统中&#xff0c;开发人员通常使用 pthread 库来进行多线程编程&#xff0c;同时需要掌握线程同步技术以避免并…

Mybatis批量更新对象数据的两种方法

说明&#xff1a;遇到一次需要批量修改对象的场景。传递一个对象集合&#xff0c;需要根据对象ID批量修改数据库数据&#xff0c;使用的是MyBatis框架。查了一些资料&#xff0c;总结出两种实现方式。 创建Demo 首先&#xff0c;创建一个简单的Demo&#xff1b; &#xff08…

Kotlin MutliPatform Demo NoteApp

简单用Kotlin实现个记录app&#xff0c;主要实现本地数据保存。支持多端运行 使用的库: voyagernapiercoroutinesktorserializationkotlinx-datetimekoinmultiplatform-settingssqldelightMVI 项目: MyNote

go并发模式之----工作池/协程池模式

常见模式之四&#xff1a;工作池/协程池模式 定义 顾名思义&#xff0c;就是有固定数量的工人&#xff08;协程&#xff09;&#xff0c;去执行批量的任务 使用场景 适用于需要限制并发执行任务数量的情况 创建一个固定大小的 goroutine 池&#xff0c;将任务分发给池中的 g…

学习:GPT-4技术报告2023.3

原文链接&#xff1a;GPT-4的 (openai.com) 摘要&#xff1a; 我们创建了 GPT-4&#xff0c;这是 OpenAI 在扩展深度学习方面的最新里程碑。GPT-4 是一个大型多模态模型&#xff08;接受图像和文本输入&#xff0c;发出文本输出&#xff09;&#xff0c;虽然在许多现实世界场…

MySQL 多表查询 连接查询 外连接

介绍 MySQL 多表查询 连接查询 内连接 外连接分为两种&#xff0c;左外和右外连接&#xff0c; 左外&#xff1a;相当于查询表1(左表)的所有数据 包含 表1和表2交集部分的数据,完全包含左表的数据 右外&#xff1a;相当于查询表2(右表)的所有数据 包含 表1和表2交集部分的数据…

c语言的数据结构:队列

1.队列存在的实现方式及其存在意义 1.1为什么队列使用单链表实现更好 动态内存分配&#xff1a;链表在C语言中通常使用动态内存分配&#xff0c;这意味着可以在运行时根据需要动态地添加或删除节点。这对于实现一个动态大小的队列非常有用&#xff0c;因为队列的大小可以在运…

达梦数据库基础操作(二):表空间操作

达梦数据库基础操作(二)&#xff1a;表空间操作 1. 表空间操作 1.1 达梦表空间介绍 表空间的概念&#xff1a; 每个DM 数据库都是由一个或者多个表空间组成&#xff0c;表空间是一个逻辑的存储容器&#xff0c;它位于逻辑结构的顶层&#xff0c;用于存储数据库中的所有数据&am…

11-orm-自研微服务框架

ORM 当开发涉及到存储数据的时候&#xff0c;往往要用到数据库&#xff0c;用的最多的就是mysql了&#xff0c;这里我们实现一个orm&#xff0c;让开发者更加便捷的操作数据库 1. Insert实现 orm的本质就是拼接sql&#xff0c;让开发者更加方便的使用 package ormimport ("…

(二)电机控制之六步方波BLDC控制方法以及注意问题

一、直流无刷电机的简介 直流无刷电机&#xff08;Brushless Direct Current Motor&#xff0c;简称BLDC电机或BL电机&#xff09;是一种先进的电动机类型&#xff0c;其设计结合了直流电机的调速性能和交流电机的结构优势。这种电机没有传统的机械换向器和碳刷组件&#xff0…
最新文章