C#不安全代码.docx

上传人:b****3 文档编号:5453760 上传时间:2022-12-16 格式:DOCX 页数:27 大小:32.08KB
下载 相关 举报
C#不安全代码.docx_第1页
第1页 / 共27页
C#不安全代码.docx_第2页
第2页 / 共27页
C#不安全代码.docx_第3页
第3页 / 共27页
C#不安全代码.docx_第4页
第4页 / 共27页
C#不安全代码.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

C#不安全代码.docx

《C#不安全代码.docx》由会员分享,可在线阅读,更多相关《C#不安全代码.docx(27页珍藏版)》请在冰豆网上搜索。

C#不安全代码.docx

C#不安全代码

18.不安全代码313

18.1不安全上下文313

18.2指针类型315

18.3固定和可移动变量318

18.4指针转换318

18.5表达式中的指针319

18.5.1指针间接寻址320

18.5.2指针成员访问320

18.5.3指针元素访问321

18.5.4address-of运算符322

18.5.5指针递增和递减323

18.5.6指针算术运算323

18.5.7指针比较324

18.5.8sizeof运算符324

18.6fixed语句325

18.7堆栈分配328

18.8动态内存分配329

1.不安全代码

如前面几章所定义,核心C#语言没有将指针列入它所支持的数据类型,从而与C和C++有着显著的区别。

作为替代,C#提供了各种引用类型,并能够创建可由垃圾回收器管理的对象。

这种设计结合其他功能,使C#成为比C或C++安全得多的语言。

在核心C#语言中,不可能有未初始化的变量、“虚”指针或者超过数组的边界对其进行索引的表达式。

这样,以往总是不断地烦扰C和C++程序的一系列错误就不会再出现了。

尽管实际上对C或C++中的每种指针类型构造,C#都设置了与之对应的引用类型,但仍然会有一些场合需要访问指针类型。

例如,当需要与基础操作系统进行交互、访问内存映射设备,或实现一些以时间为关键的算法时,若没有访问指针的手段,就不可能或者至少很难完成。

为了满足这样的需求,C#提供了编写不安全代码(unsafecode)的能力。

在不安全代码中,可以声明和操作指针,可以在指针和整型之间执行转换,还可以获取变量的地址,等等。

在某种意义上,编写不安全代码很像在C#程序中编写C代码。

无论从开发人员还是从用户角度来看,不安全代码事实上都是一种“安全”功能。

不安全代码必须用修饰符unsafe明确地标记,这样开发人员就不会误用不安全功能,而执行引擎将确保不会在不受信任的环境中执行不安全代码。

1.1不安全上下文

C#的不安全功能仅用于不安全上下文中。

不安全上下文是通过在类型或成员的声明中包含一个unsafe修饰符或者通过使用unsafe-statement引入的:

∙类、结构、接口或委托的声明可以包含一个unsafe修饰符,在这种情况下,该类型声明的整个文本范围(包括类、结构或接口的体)被认为是不安全上下文。

∙在字段、方法、属性、事件、索引器、运算符、实例构造函数、析构函数或静态构造函数的声明中,也可以包含一个unsafe修饰符,在这种情况下,该成员声明的整个文本范围被认为是不安全上下文。

∙unsafe-statement使得可以在block内使用不安全上下文。

该语句关联的block的整个文本范围被认为是不安全上下文。

下面显示了关联的语法扩展。

为简洁起见,用省略号(...)表示前几章中出现过的产生式。

class-modifier:

...

unsafe

struct-modifier:

...

unsafe

interface-modifier:

...

unsafe

delegate-modifier:

...

unsafe

field-modifier:

...

unsafe

method-modifier:

...

unsafe

property-modifier:

...

unsafe

event-modifier:

...

unsafe

indexer-modifier:

...

unsafe

operator-modifier:

...

unsafe

constructor-modifier:

...

unsafe

destructor-declaration:

attributesoptexternoptunsafeopt~identifier()destructor-body

attributesoptunsafeoptexternopt~identifier()destructor-body

static-constructor-modifiers:

externoptunsafeoptstatic

unsafeoptexternoptstatic

externoptstaticunsafeopt

externoptstaticunsafeopt

staticexternoptunsafeopt

staticunsafeoptexternopt

embedded-statement:

...

unsafe-statement

unsafe-statement:

unsafeblock

在下面的示例中

publicunsafestructNode

{

publicintValue;

publicNode*Left;

publicNode*Right;

}

在结构声明中指定的unsafe修饰符导致该结构声明的整个文本范围成为不安全上下文。

因此,可以将Left和Right字段声明为指针类型。

上面的示例还可以编写为

publicstructNode

{

publicintValue;

publicunsafeNode*Left;

publicunsafeNode*Right;

}

此处,字段声明中的unsafe修饰符导致这些声明被认为是不安全上下文。

除了建立不安全上下文从而允许使用指针类型外,unsafe修饰符对类型或成员没有影响。

在下面的示例中

publicclassA

{

publicunsafevirtualvoidF(){

char*p;

...

}

}

publicclassB:

A

{

publicoverridevoidF(){

base.F();

...

}

}

A中F方法上的unsafe修饰符直接导致F的文本范围成为不安全上下文并可以在其中使用语言的不安全功能。

在B中对F的重写中,不需要重新指定unsafe修饰符,除非B中的F方法本身需要访问不安全功能。

当指针类型是方法签名的一部分时,情况略有不同

publicunsafeclassA

{

publicvirtualvoidF(char*p){...}

}

publicclassB:

A

{

publicunsafeoverridevoidF(char*p){...}

}

此处,由于F的签名包括指针类型,因此它只能出现在不安全上下文中。

然而,为设置此不安全上下文,既可以将整个类设置为不安全的(如A中的情况),也可以仅在方法声明中包含一个unsafe修饰符(如B中的情况)。

1.2指针类型

在不安全上下文中,type(第4章)可以是pointer-type,也可以是value-type或reference-type。

但是,pointer-type也能在不安全上下文以外的typeof表达式(第7.5.11节)中使用,因为此类使用不是不安全的。

type:

value-type

reference-type

pointer-type

pointer-type可表示为unmanaged-type后跟一个*标记,或者关键字void后跟一个*标记:

pointer-type:

unmanaged-type*

void*

unmanaged-type:

type

指针类型中,在*前面指定的类型称为该指针类型的目标类型(referenttype)。

它表示该指针类型的值所指向的变量的类型。

与引用(引用类型的值)不同,指针不受垃圾回收器跟踪(垃圾回收器并不知晓指针和它们指向的数据)。

出于此原因,不允许指针指向引用或者包含引用的结构,并且指针的目标类型必须是unmanaged-type。

unmanaged-type是任何不是reference-type并且在任何嵌套级别都不包含reference-type字段的类型。

换句话说,unmanaged-type是下列类型之一:

∙sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal或bool。

∙任何enum-type。

∙任何pointer-type。

∙任何由用户定义的只包含unmanaged-type字段的struct-type。

将指针和引用进行混合使用时的基本规则是;引用(对象)的目标可以包含指针,但指针的目标不能包含引用。

下表给出了一些指针类型的示例:

示例

说明

byte*

指向byte的指针

char*

指向char的指针

int**

指向int的指针的指针

int*[]

一维数组,它的元素是指向int的指针

void*

指向未知类型的指针

对于某个给定实现,所有的指针类型都必须具有相同的大小和表示形式。

与C和C++不同,在C#中,当在同一声明中声明多个指针时,*只与基础类型写在一起,而不充当每个指针名称的前缀标点符号。

例如

int*pi,pj;//NOTasint*pi,*pj;

类型为T*的一个指针的值表示类型为T的一个变量的地址。

指针间接寻址运算符*(第18.5.1节)可用于访问此变量。

例如,给定

int*类型的变量P,则表达式*P表示int变量,该变量的地址就是P的值。

与对象引用类似,指针可以是null。

如果将间接寻址运算符应用于null指针,则其行为将由实现自己定义。

值为null的指针表示为将该指针的所有位都置零。

void*类型表示指向未知类型的指针。

因为目标类型是未知的,所以间接寻址运算符不能应用于void*类型的指针,也不能对这样的指针执行任何算术运算。

但是,void*类型的指针可以强制转换为任何其他指针类型(反之亦然)。

指针类型是一个单独类别的类型。

与引用类型和值类型不同,指针类型不从object继承,而且不存在指针类型和object之间的转换。

具体而言,指针不支持装箱和拆箱(第4.3节)操作。

但是,允许在不同指针类型之间以及指针类型与整型之间进行转换。

在第18.4节中对此进行了描述。

pointer-type可用作易失字段的类型(第10.4.3节)。

虽然指针可以作为ref或out参数传递,但这样做可能会导致未定义的行为,例如,指针可能被设置为指向一个局部变量,而当调用方法返回时,该局部变量可能已不存在了;或者指针曾指向一个固定对象,但当调用方法返回时,该对象不再是固定的了。

例如:

usingSystem;

classTest

{

staticintvalue=20;

unsafestaticvoidF(outint*pi1,refint*pi2){

inti=10;

pi1=&i;

fixed(int*pj=&value){

//...

pi2=pj;

}

}

staticvoidMain(){

inti=10;

unsafe{

int*px1;

int*px2=&i;

F(outpx1,refpx2);

Console.WriteLine("*px1={0},*px2={1}",

*px1,*px2);//undefinedbehavior

}

}

}

方法可以返回某一类型的值,而该类型可以是指针。

例如,给定一个指向连续的int值序列的指针、该序列的元素个数,和另外一个int值(value),下面的方法将在该整数序列中查找与该value匹配的值,若找到匹配项,则返回该匹配项的地址;否则,它将返回null:

unsafestaticint*Find(int*pi,intsize,intvalue){

for(inti=0;i

if(*pi==value)

returnpi;

++pi;

}

returnnull;

}

在不安全上下文中,可以使用下列几种构造操作指针:

∙*运算符可用于执行指针间接寻址(第18.5.1节)。

∙->运算符可用于通过指针访问结构的成员(第18.5.2节)。

∙[]运算符可用于索引指针(第18.5.3节)。

∙&运算符可用于获取变量的地址(第18.5.4节)。

∙++和--运算符可以用于递增和递减指针(第18.5.5节)。

∙+和-运算符可用于执行指针算术运算(第18.5.6节)。

∙==、!

=、<、>、<=和=>运算符可以用于比较指针(第18.5.7节)。

∙stackalloc运算符可用于从调用堆栈(第18.7节)中分配内存。

∙fixed语句可用于临时固定一个变量,以便可以获取它的地址(第18.6节)。

1.3固定和可移动变量

address-of运算符(第18.5.4节)和fixed语句(第18.6节)将变量划分为两个类别:

固定变量(fixedvariable)和可移动变量(moveablevariable)。

固定变量驻留在不受垃圾回收器的操作影响的存储位置中。

(固定变量的示例包括局部变量、值参数和由取消指针引用而创建的变量。

)另一方面,可移动变量则驻留在会被垃圾回收器重定位或处置的存储位置中。

(可移动变量的示例包括对象中的字段和数组的元素。

&运算符(第18.5.4节)允许不受限制地获取固定变量的地址。

但是,由于可移动变量会受到垃圾回收器的重定位或处置,因此可移动变量的地址只能使用fixed语句(第18.6节)获取,而且该地址只在此fixed语句的生存期内有效。

准确地说,固定变量是下列之一:

∙用引用局部变量或值参数的simple-name(第7.5.2节)表示的变量。

∙用V.I形式的member-access(第7.5.4节)表示的变量,其中V是struct-type的固定变量。

∙用*P形式的pointer-indirection-expression(第18.5.1节)、P->I形式的pointer-member-access(第18.5.2节)或P[E]形式的pointer-element-access(第18.5.3节)表示的变量。

所有其他变量都属于可移动变量。

请注意静态字段属于可移动变量。

还请注意即使赋予ref或out形参的实参是固定变量,它们仍属于可移动变量。

最后请注意,由取消指针引用而产生的变量总是属于固定变量。

1.4指针转换

在不安全上下文中,可供使用的隐式转换的集合(第6.1节)也扩展为包括以下隐式指针转换:

∙从任何pointer-type到void*类型。

∙从null类型到任何pointer-type。

另外,在不安全上下文中,可供使用的显式转换的集合(第6.2节)也扩展为包括以下显式指针转换:

∙从任何pointer-type到任何其他pointer-type。

∙从sbyte、byte、short、ushort、int、uint、long或ulong到任何pointer-type。

∙从任何pointer-type到sbyte、byte、short、ushort、int、uint、long或ulong。

最后,在不安全上下文中,标准隐式转换的集合(第6.3.1节)包括以下指针转换:

∙从任何pointer-type到void*类型。

两个指针类型之间的转换永远不会更改实际的指针值。

换句话说,从一个指针类型到另一个指针类型的转换不会影响由指针给出的基础地址。

当一个指针类型被转换为另一个指针类型时,如果没有将得到的指针正确地对指向的类型对齐,则当结果被取消引用时,该行为将是未定义的。

一般情况下,“正确对齐”的概念是可传递的:

如果指向类型A的指针正确地与指向类型B的指针对齐,而此指向类型B的指针又正确地与指向类型C的指针对齐,则指向类型A的指针将正确地与指向类型C的指针对齐。

请考虑下列情况,其中具有一个类型的变量被通过指向一个不同类型的指针访问:

charc='A';

char*pc=&c;

void*pv=pc;

int*pi=(int*)pv;

inti=*pi;//undefined

*pi=123456;//undefined

当一个指针类型被转换为指向字节的指针时,转换后的指针将指向原来所指变量的地址中的最低寻址字节。

连续增加该变换后的指针(最大可达到该变量所占内存空间的大小),将产生指向该变量的其他字节的指针。

例如,下列方法将double型变量中的八个字节的每一个显示为一个十六进制值:

usingSystem;

classTest

{

unsafestaticvoidMain(){

doubled=123.456e23;

unsafe{

byte*pb=(byte*)&d;

for(inti=0;i

Console.Write("{0:

X2}",*pb++);

Console.WriteLine();

}

}

}

当然,产生的输出取决于字节存储顺序(Endianness)。

指针和整数之间的映射由实现定义。

但是,在具有线性地址空间的32位和64位CPU体系结构上,指针和整型之间的转换通常与uint或ulong类型的值与这些整型之间的对应方向上的转换具有完全相同的行为。

1.5表达式中的指针

在不安全上下文中,表达式可能产生指针类型的结果,但是在不安全上下文以外,表达式为指针类型属于编译时错误。

准确地说,在不安全上下文以外,如果任何simple-name(第7.5.2节)、member-access(第7.5.4节)、invocation-expression(第7.5.5节)或element-access(第7.5.6节)属于指针类型,则将发生编译时错误。

在不安全上下文中,primary-no-array-creation-expression(第7.5节)和unary-expression(第7.6节)产生式允许使用下列附加构造:

primary-no-array-creation-expression:

...

pointer-member-access

pointer-element-access

sizeof-expression

unary-expression:

...

pointer-indirection-expression

addressof-expression

以下几节对这些构造进行了描述。

相关的语法暗示了不安全运算符的优先级和结合性。

1.5.1指针间接寻址

pointer-indirection-expression包含一个星号(*),后跟一个unary-expression。

pointer-indirection-expression:

*unary-expression

一元*运算符表示指针间接寻址并且用于获取指针所指向的变量。

计算*P得到的结果(其中P为指针类型T*的表达式)是类型为T的一个变量。

将一元*运算符应用于void*类型的表达式或者应用于不是指针类型的表达式属于编译时错误。

将一元*运算符应用于null指针的效果是由实现定义的。

具体而言,不能保证此操作会引发System.NullReferenceException。

如果已经将无效值赋给指针,则一元*运算符的行为是未定义的。

通过一元*运算符取消指针引用有时会产生无效值,这些无效值包括:

没能按所指向的类型正确对齐的地址(请参见第18.4节中的示例)和超过生存期的变量的地址。

出于明确赋值分析的目的,通过计算*P形式的表达式产生的变量被认为是初始化赋过值的(第5.3.1节)。

1.5.2指针成员访问

pointer-member-access包含一个primary-expression,后跟一个“->”标记,最后是一个identifier。

pointer-member-access:

primary-expression->identifier

在P->I形式的指针成员访问中,P必须是除void*以外的某个指针类型的表达式,而I必须表示P所指向的类型的可访问成员。

P->I形式的指针成员访问的计算方式与(*P).I完全相同。

有关指针间接寻址运算符(*)的说明,请参见第18.5.1节。

有关成员访问运算符(.)的说明,请参见第7.5.4节。

在下面的示例中

usingSystem;

structPoint

{

publicintx;

publicinty;

publicoverridestringToString(){

return"("+x+","+y+")";

}

}

classTest

{

staticvoidMain(){

Pointpoint;

unsafe{

Point*p=&point;

p->x=10;

p->y=20;

Console.WriteLine(p->ToString());

}

}

}

->运算符用于通过指针访问结构中的字段和调用结构中的方法。

由于P->I操作完全等效于(*P).I,因此Main方法可以等效地编写为:

classTest

{

staticvoidMain(){

Pointpoint;

unsafe{

Point*p=&point;

(*p).x=10;

(*p).y=20;

Console.WriteLine((*p).ToString());

}

}

}

1.5.3指针元素访问

pointer-element-access包括一个primary-no-array-creation-expression,后跟一个用“[”和“]”括起来的表达式。

pointer-element-access:

primary-no-array-creation-expressi

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1