C#基础之不安全代码讲解

news/2025/2/12 11:58:48/文章来源:https://www.cnblogs.com/jingzh/p/18580049

目录
  • 1 不安全代码
    • 1.1 简介
    • 1.2 指针变量
    • 1.3 编译不安全代码
    • 1.4 使用示例
      • 1.4.1 简单使用
      • 1.4.2 使用指针检索数据值
      • 1.4.3 传递指针作为方法的参数
      • 1.4.4 使用指针访问数组元素
    • 1.5 fixed
      • 1.5.1 为什么需要 fixed
      • 1.5.2 为什么 int *p 和 int[] p 是不同的类型
      • 1.5.3 示例
    • 1.6 stackalloc

1 不安全代码

1.1 简介

当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量不安全代码非托管代码是指使用了指针变量的代码块。

1.2 指针变量

指针 是值为另一个变量的地址的变量,即,内存位置的直接地址。就像其他变量或常量,必须在使用指针存储其他变量地址之前声明指针。
点击此处了解 C语言之指针详解
指针变量声明的一般形式为:

type* var-name;

下面是指针类型声明的实例:

实例 描述
int* p p 是指向整数的指针
double* p p 是指向双精度数的指针
float* p p 是指向浮点数的指针
int** p p 是指向整数的指针的指针
int*[] p p 是指向整数的指针的一维数组
char* p p 是指向字符的指针
void* p p 是指向未知类型的指针

在同一个声明中声明多个指针时,星号 * 仅与基础类型一起写入;而不是用作每个指针名称的前缀。 例如:

int* p1, p2, p3;     // 正确  
int *p1, *p2, *p3;   // 错误 

1.3 编译不安全代码

为了编译不安全代码,必须切换到命令行编译器指定 /unsafe 命令行。
例如,为了编译包含不安全代码的名为 prog1.cs 的程序,需在命令行中输入命令:csc /unsafe prog1.cs
如果使用的是 Visual Studio,那么您需要在项目属性中启用不安全代码。

步骤如下:

  • 通过双击资项目下的属性(properties)节点,打开项目属性
    在这里插入图片描述

  • 点击 Build 标签页。

  • 选择选项"Allow unsafe code"
    在这里插入图片描述

1.4 使用示例

1.4.1 简单使用

using System;
namespace UnsafeCodeApplication
{class Program{static unsafe void Main(string[] args){int var = 20;int* p = &var;Console.WriteLine("Data is: {0} ",  var);Console.WriteLine("Address is: {0}",  (int)p);Console.ReadKey();}}
}结果:
Data is: 20
Address is: 99215364

也可以不用声明整个方法作为不安全代码,只需要声明方法的一部分作为不安全代码

1.4.2 使用指针检索数据值

使用 ToString() 方法检索存储在指针变量所引用位置的数据

using System;
namespace UnsafeCodeApplication
{class Program{public static void Main(){unsafe{int var = 20;int* p = &var;Console.WriteLine("Data is: {0} " , var);Console.WriteLine("Data is: {0} " , p->ToString());Console.WriteLine("Address is: {0} " , (int)p);}Console.ReadKey();}}
}结果:
Data is: 20
Data is: 20
Address is: 77128984

1.4.3 传递指针作为方法的参数

向方法传递指针变量作为方法的参数

using System;
namespace UnsafeCodeApplication
{class TestPointer{public unsafe void swap(int* p, int *q){int temp = *p;*p = *q;*q = temp;}public unsafe static void Main(){TestPointer p = new TestPointer();int var1 = 10;int var2 = 20;int* x = &var1;int* y = &var2;Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);p.swap(x, y);Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);Console.ReadKey();}}
}结果:
Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10

1.4.4 使用指针访问数组元素

在 C# 中,数组名称和一个指向与数组数据具有相同数据类型的指针是不同的变量类型。例如,int *pint[] p 是不同的类型。可以增加指针变量 p,因为它在内存中不是固定的,但是数组地址在内存中是固定的,所以不能增加数组 p。
因此,如果您需要使用指针变量访问数组数据,可以像我们通常在 C 或 C++ 中所做的那样,使用 fixed 关键字来固定指针。

using System;
namespace UnsafeCodeApplication
{class TestPointer{public unsafe static void Main(){int[]  list = {10, 100, 200};fixed(int *ptr = list)/* 显示指针中数组地址 */for ( int i = 0; i < 3; i++){Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));}Console.ReadKey();}}
}结果:
Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200

1.5 fixed

C#中的指针 为什么 还得用 fixed 关键字来固定指针
C# 中,使用 fixed 关键字来固定指针的目的是为了保证内存的稳定性,这是因为 C# 在托管环境中使用垃圾回收机制(Garbage Collection, GC)。与 C 语言不同,C# 的对象通常在 托管堆(Managed Heap) 上分配,垃圾回收器会在运行时根据需要移动对象来优化内存,这可能导致指针指向的内存地址发生变化。

1.5.1 为什么需要 fixed

托管对象的移动:垃圾回收器为了整理内存,可能会移动托管堆中的对象,造成指针指向的地址失效或不正确。这是 C# 中的指针需要 fixed 的根本原因。

  • 在 C 语言中,所有内存管理都是手动的,分配和释放内存完全由程序员控制,没有垃圾回收机制,因此不需要 fixed 关键字,
  • C# 运行在托管环境中,有垃圾回收机制,会在后台管理内存,自动回收无用对象。因此,C# 中的指针需要 fixed 来避免内存移动带来的不稳定性
  • fixed 的作用:fixed 关键字会将对象的内存锁定在固定位置,确保垃圾回收器不会移动它,从而避免指针失效。这在访问托管对象中的非托管内存时尤其重要,例如固定数组或字符串的内存位置时

1.5.2 为什么 int *p 和 int[] p 是不同的类型

C# 中,int* pint[] p 其实并不是同一类型,它们虽然都可以指向数组的起始位置,但它们在底层实现使用方式上存在显著差异。

以下是它们的具体区别:

  • 类型不同
    • int* p:这是一个指针类型,表示一个指向 int 类型的内存地址。需要使用 unsafe 代码块,因为指针操作在 C# 中是非托管的。
    • int[] p:这是一个托管数组,存储在托管堆上,并由垃圾回收器管理。它有长度信息和一些附加的元数据,不是一个简单的内存地址。
  • 使用方式不同
    • 指针 (int*)
      指针是非托管的,可以通过算术运算(如 p++)来遍历内存,因为它直接指向内存地址。
      指针变量 int* p 只存储一个地址,没有数组的长度等元数据信息。
    • 数组 (int[])
      数组是托管对象,包含长度和类型信息。它本质上是一个引用类型,指向托管堆上的数组对象。
      C# 不允许对 int[] 执行指针算术运算,也不能通过增加数组变量 p 的值来遍历数组。

为什么 int*int[] 是不同类型

  • 托管 & 非托管:int[] 是托管类型,由 CLR(公共语言运行时)管理。int* 是非托管类型,直接操作内存地址。
  • 内存布局:int[] 包含长度信息和其他元数据;而 int* 是直接的地址,不包含任何附加信息。
指针示例
unsafe
{int value = 10;int* p = &value;Console.WriteLine(*p); // 输出值p++; // 增加指针地址
}
数组示例
int[] array = { 1, 2, 3 };
Console.WriteLine(array[0]); // 使用索引访问

1.5.3 示例

使用 fixed 关键字的示例:

unsafe
{int[] numbers = { 1, 2, 3, 4, 5 };fixed (int* p = numbers) // 固定数组的内存位置{for (int i = 0; i < 5; i++){Console.WriteLine(*(p + i)); // 使用指针访问数组}} // 离开 fixed 代码块后,指针失效
}

1.6 stackalloc

unsafe不安全环境中,我们还可以通过stackalloc在堆栈上分配内存,因为在堆栈上分配的内存不受内存管理器管理,因此其相应的指针不需要固定。

static unsafe void Main(string[] args)
{int *ptr = stackalloc int[1] ;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDA+WSL2实现本地linux动态调试

1、首先在ida安装目录找到dbgsrv这个文件夹,打开后把“linux_server”这个文件拖到你的linux中(我放在/root位置)2、然后赋予两个文件权限(linux-server和要调试的文件) chmod +x /root/linux_server chmod +x 你的待调试文件位置然后运行调试组件 /root/linux_server64参…

英特尔固态硬盘维修数据恢复

固态硬盘(SSD)在维修和数据恢复方面,需要根据具体情况采取不同的方法。以下是一些常见的固态硬盘维修和数据恢复方法: 一、检查硬件连接与电源 检查连接线: 打开计算机主机箱,检查SATA或NVMe接口的连接线是否插紧,是否有松动或断裂。 重新插拔连接线,然后重启计算机,看…

WEB AK赛-web2_观星

一看就是sql注入题 尝试了一下发现过滤的东西还挺多的(union,like,=,’,空格,and,if,逗号,ascii,sleep)等 不能用正常的布尔盲注:1^if(ascii(substr(flag,1,1))=104,1,0) 题目过滤了if 看了wp才知道有这种方式:case(A)when(B)then(C)else(D)end 绕过过滤: 过滤了空格可以用(…

【学校训练记录】12月个人训练赛1个人题解

A对于n本书拿出k本较为难实现,但是从n本书里拿出n-k本就容易多了 对于n本书里拿一本为特殊情况,不管怎么拿都为0 对于n本书里拿n-k本的话,我们假设拿的最后一本为i那么 他就是拿出n-k-1本书的情况再加上拿出第i本的情况 其中差值变化为拿出n-k-1本书的值,加上我abs(w[i]-w[…

MySQL底层概述—7.优化原则及慢查询

大纲 1.Explain概述 2.Explain详解 3.索引优化数据准备 4.索引优化原则详解 5.慢查询设置与测试 6.慢查询SQL优化思路1.Explain概述 使用Explain关键字可以模拟查询优化器来执行SQL查询语句,从而知道MySQL是如何处理SQL语句的,从而分析出查询语句和表结构的性能瓶颈。MySQL查…

升鲜宝生鲜配送供应链管理系统Mysql表结构数据字典的生成小工具V0.01

最近最近要交付升鲜宝生鲜配送供应链管理系统源代码给上海的客户,需要将蓝湖UI设计图及数据字典交接给别人。在网上找了半天没有找到合适的根据Mysql生成Word数据字典,自己就写了几行代码,记录一下.后面可能会继续改造。主要的代码如下:using System; using System.Collect…

非递归线段树实现

ZKW 非递归线段树 参考文章:线段树详解(非递归版)_非递归线段树-CSDN博客 建树: 原数组[1,n] 存在线段树的 [2,n+1] (为了方便区间查询,要空出第一个节点和最后一个节点) 区间查询 若查询[L,R] 区间, 选取L-1和 R+1 两个节点, 向上寻找.每次到达父节点 L-1 查看自己的父亲的右…

hot100-一刷-01哈希(共3道题)

1.两数之和 题目链接 题目描述代码实现 分析:暴力的话就是两个for循环依次寻找相加为target的两个数。 用一个map记录已经遍历过的数,其中key就用这个数的字面值,value就存它的下标。 判断是否相加为taget的时候,只需要看map中是否有target-nums[i]就可以,说明当前的nums[…

Activiti 学习笔记

工作流概述 每一项业务的开始和结束,都可以理解为一个工作流,例如,公司的费用报销的基本流程如下:员工先提出费用报销申请,提交给部门领导,部门领导审批后,提交给财务部门审批,审批完成后,通知提出申请的员工可以报销,报销流程结束。整个流程按照步骤完成,这就是一个…

高级程序语言设计课第九次作业

14.17.314.17.414.17.514.17.1014.17.1114.18.314.18.4 a: b: 14.18.5

WEB AK赛-web1_观字

对这段代码进行解析: substr($url, 0, 7) 截取url变量前7位判断是否是http协议 preg_match() 正则表达式检查 URL 中是否包含一些潜在的危险字符: 过滤了:., ;, |, <, >, *, %, ^, () 这题只需要绕过"."即可。 尝试使用url编码不可以 在curl中可以使用"…

Fail pg walkthrough Intermediate

nmap ┌──(root㉿kali)-[/home/ftpuserr] └─# nmap -p- -A 192.168.159.126 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-01 02:54 UTC Nmap scan report for 192.168.159.126 Host is up (0.071s latency). Not shown: 65533 closed tcp ports (reset) PORT …