第5 章• 指针和数组79
数组声明和存储
除了第3 章中说明的动态关联数组之外,D还支持标量数组。标量数组是一组固定长度的连
续内存位置,每个位置存储一个相同类型的值。可通过使用从零开始的整数引用每个位置
来访问标量数组。标量数组直接对应于C 和C++ 中数组的概念和语法。虽然在D中使用标
量数组不如使用关联数组和其高级对应项(聚合)那么频繁,但在访问C 中声明的现有操
作系统数组数据结构时需要标量数组。聚合将在第9 章中介绍。
由5 个整数组成的D标量数组应这样声明:使用类型int,并在声明后加上用方括号括起来
的元素数目,如下所示:
int a[5];
下图显示了数组存储的可视表示:
图5–1标量数组表示
D表达式a[0] 用于引用第一个数组元素,a[1] 引用第二个数组元素,依此类推。从语法的
角度来看,标量数组和关联数组非常相似。可声明由5 个整数(通过整数键引用)组成的
关联数组,如下所示:
int a[int];
并且使用表达式a[0] 来引用此数组。但从存储和实现角度来看,两个数组有很大的不同。
静态数组a 由5 个连续内存位置(编号从0 开始)组成,并且索引表示存储空间中为此数组
分配的偏移。另一方面,关联数组没有预定义大小,并且不会将元素存储在连续内存位置
中。此外,关联数组键与相应的值存储位置无关。可访问关联数组元素a[0] 和a[-5],并
且DTrace 只分配两个词的存储空间,它们可能连续,也可能不连续。关联数组键是相应值
的抽象名称,与值存储位置无关。
如果使用初始赋值创建数组,并将一个整数表达式用作数组索引(如a[0] = 2),则D 编
译器将始终创建新的关联数组,即使在此表达式中a 还可解释为对标量数组的赋值。在此
情况下,必须预先声明标量数组,以便D编译器能够了解数组大小的定义并推断此数组为
标量数组。
数组声明和存储
80 Solaris 动态跟踪指南• 2006 年7 月
指针和数组关系
就像在ANSI-C 中一样,指针和数组在D中有特殊关系。数组由与其第一个存储位置的地址
关联的变量表示。指针也是具有所定义类型的存储位置的地址,所以D允许将array [ ] 索引
表示法与指针变量和数组变量配合使用。例如,以下两个D代码段的含义等价:
p = &a[0]; trace(a[2]);
trace(p[2]);
在左边代码段中,通过对表达式a[0] 应用& 运算符,指针p 被赋给a 中的第一个数组元素
的地址。表达式p[2] 将跟踪第三个数组元素(索引为2)的值。因为p 现在包含与a 关联的
同一地址,所以此表达式将生成与a[2] 相同的值,如右侧代码段中所示。等价的一个后果
就是C 和D允许您访问任何指针或数组的任何索引。编译器或DTrace 运行时环境不会执行
数组界限检查。如果访问超出数组预定义值的内存,则会产生意外结果或者DTrace 会报告
无效地址错误,如先前示例中所示。与平常一样,不会破坏DTrace 本身或操作系统,但您
需要调试D程序。
指针与数组之间的区别在于:指针变量引用一个单独的存储块,该存储块包含另一存储空
间的整数地址。数组变量会对数组存储空间本身命名,而不是对包含数组位置的整数位置
命名。下图中显示了此区别:
图5–2指针和数组存储
如果尝试对指针和标量数组赋值,则D语法中将显示此区别。如果x 和y 都是指针变量,则
表达式x = y 合法;表达式只将y 中的指针地址复制到x 命名的存储位置。如果x 和y 是标
量数组变量,则表达式x = y 不合法。在D 中不能将数组作为一个整体赋值。但是,可在允
许使用指针的任何上下文中使用数组变量或符号名。如果p 是指针而a 是数组,则允许使用
语句p = a,此语句等价于语句p = &a[0]。
指针和数组关系
第5 章• 指针和数组81
指针运算
因为指针只是用作内存中其他对象地址的整数,所以D提供了一组功能来对指针执行运
算。但是,指针运算与整数运算并不完全相同。指针运算通过将指针引用的类型大小与操
作数相乘或相除,来隐式调整基础地址。以下D代码段将说明此属性:
int *x;
BEGIN
{
trace(x);
trace(x + 1);
trace(x + 2);
}
此代码段创建整数指针x 并跟踪其值,首先将值递增1,然后将值递增2。如果创建并执行
此程序,DTrace 会报告整数值0,4 和8。
因为x 是指向整数的指针(大小为4 字节),递增x 将对基础指针值增加4。在使用指针来
引用连续存储位置(如数组)时,此属性很有用。例如,如果将数组a 的地址赋给x(如图
5–2 中所示),则表达式x + 1 将等价于表达式&a[1]。类似地,表达式*(x + 1) 将引用值
a[1]。每次使用+=、+ 或++ 运算符递增指针的值时,D编译器将实现指针运算。
在从左边的指针中减去某个整数时、从一个指针中减去另一个指针时或者对指针应用-- 运
算符时,也将应用指针运算。例如,以下D程序将跟踪结果2:
int *x, *y;
int a[5];
BEGIN
{
x = &a[0];
y = &a[2];
指针运算
82 Solaris 动态跟踪指南• 2006 年7 月
trace(y - x);
}
通用指针
有时在D程序中,在不指定指针引用的数据类型的情况下表示或处理通用指针地址会很有
用。可使用类型void *(其中关键字void 表示缺少特定信息)或使用内置类型别名
uintptr_t(它表示对应当前数据模型中某个指针的大小的无符号整数类型别名)来指定通
用指针。不能对void * 类型的对象应用指针运算,并且只有在先将这些指针的类型强制转
换为另一类型之后才能取消对它们的引用。在需要对指针值执行整数运算时,可将指针强
制转换为uintptr_t 类型。
可在需要指向另一数据类型的指针的任何上下文(如关联数组元组表达式或赋值语句的右
侧)中使用指向void 的指针。同样,可在需要指向void 的指针的上下文中使用指向任何数
据类型的指针。要用指向非void 类型的指针来代替另一非void 指针类型,需要使用显式强
制类型转换。必须始终使用显式强制类型转换将指针转换为整数类型(如uintptr_t),或
者将这些整数转换回相应的指针类型。
多维数组
多维标量数组在D中使用较少,提供它们是为了与ANSI-C 兼容,以及观察和访问在C 中使
用此功能创建的操作系统数据结构。通过基本类型和其后用方括号([ ])括起来的一系列
连续的标量数组大小来声明多维数组。例如,要声明固定大小的二维矩形数组(12 行x 34
列),应编写以下声明:
int a[12][34];
可使用类似表示法来访问多维标量数组。例如,要访问存储在第0 行第1 列中的值,应编写
以下D表达式:
a[0][1]
多维标量数组值的存储位置是通过将行号与声明的总列数相乘并加上列号计算得出的。
应该注意的是,不要将多维数组语法与关联数组访问的D语法相混淆(即a[0][1] 与a[0,
1] 不同)。如果将不兼容的元组与关联数组配合使用,或者尝试通过关联数组来访问标量
数组,则D编译器将报告相应的错误消息并拒绝编译程序。
多维数组
第5 章• 指针和数组83
指向DTrace 对象的指针
D编译器禁止使用& 运算符来获取指向DTrace 对象(如关联数组、内置函数和变量)的指
针。禁止获取这些变量的地址,可以使DTrace 运行时环境根据需要在探测器触发之间重新
对变量定位,以便更有效地管理程序所需的内存。如果创建复合结构,则可以构造不检索
DTrace 对象存储的内核地址的表达式。应避免在D程序中创建这类表达式。如果需要使用
这样的表达式,一定不要在探测器触发之间高速缓存地址。
在ANSI-C 中,还可使用指针执行间接函数调用或赋值,如将使用一元* 取消引用运算符的
表达式放在赋值运算符的左边。在D中,不允许这些使用指针的表达式类型。您只能通过
使用名称或对D标量数组或关联数组应用数组索引运算符[] 来直接对D变量赋值。只能通
过第10 章中指定的名称来调用DTrace 环境定义的函数。D中不允许那些使用指针进行的间
接函数调用。
指针和地址空间
指针是一个地址,用于将某些虚拟地址空间转换为物理内存块。DTrace 在操作系统内核本
身的地址空间中执行D程序。整个Solaris 系统将管理许多地址空间:操作系统内核使用一
个地址空间,每个用户进程也使用一个地址空间。因为每个地址空间表现为它可以访问系
统上的所有内存,所以同一虚拟地址指针值可在地址空间中重复使用,但会转换为不同的
物理内存。因此,在编写使用指针的D程序时,必须注意对应于要使用的指针的地址空
间。
例如,如果使用syscall 提供器来检测将指向整数或整数数组的指针作为参数(如
pipe(2))的系统调用入口,则使用* 或[] 运算符来对指针或数组取消引用是无效操作,因
为要使用的地址是执行系统调用的用户进程地址空间中的地址。在D中,对此地址应用*
或[] 运算符将导致内核地址空间访问,这又将导致无效地址错误或对D程序返回意外数据
(取决于地址是否与有效内核地址相匹配)。
要通过DTrace 探测器访问用户进程内存,必须对用户地址空间指针应用第10 章中介绍的
copyin()、copyinstr() 或copyinto() 函数。要注意的是,为了避免混淆,在编写D程序时
应对存储用户地址的变量进行相应的命名和注释。还可将用户地址存储为uintptr_t,这样
就不会无意中编译取消引用它们的D代码。第33 章中介绍了在用户进程中使用DTrace 的
方法。
指向DTrace 对象的指针
84 Solaris 动态跟踪指南• 2006 年7 月
字符串
DTrace 可以对跟踪和处理字符串提供支持。本章介绍用于声明和处理字符串的完整的D语
言功能集。与ANSI-C 不同,D中的字符串具有自己的内置类型和运算符支持,以便您可以
在跟踪程序中轻松而明确地使用这些字符串。
字符串表示
在DTrace 中,字符串表示为由空字节(即,值为零的字节,通常写为’\0’)结尾的字符数
组。字符串的可见部分的长度是可变的,具体取决于空字节的位置,但是DTrace 将以大小
固定的数组存储每个字符串,以便每个探测器跟踪一致的数据量。字符串的长度不能超过
此预定义的字符串限制,但可以在D程序中或在dtrace 命令行上通过调整strsize 选项来
修改该限制。有关可调选项的更多信息,请参阅第16 章。缺省字符串限制为256 字节。
D 语言提供了显式string 类型,而不是使用char * 类型来引用字符串。string 类型与char
* 等效,因为它是字符序列的地址,但在将D编译器和D函数(如trace())应用于string
类型的表达式时,可提供增强功能。例如,在需要跟踪字符串的实际字节数时,字符串类
型消除了char * 类型的不确定性。在D 语句中:
trace(s);
如果s 属于类型char *,则DTrace 将跟踪指针s 的值(即,跟踪一个整数地址值)。在D
语句中:
trace(*s);
根据* 运算符的定义,D编译器将取消对指针s 的引用并跟踪该位置的单个字符。对于处理
设计用于引用单个字符或由字节大小整数(非字符串且不以空字节结尾)构成的数组的字
符指针,这些行为很重要。在D语句中:
trace(s);
6第6 章
85
如果s 属于string 类型,则该string 类型指示D编译器,需要DTrace 跟踪字符地址存储在
变量s 中的以空字符结尾的字符串。也可以对string 类型的表达式执行词法比较,如第87
页中的“字符串比较”中所说明。
字符串常量
字符串常量引在双引号(") 中,D编译器自动为其分配string 类型。您可以定义任意长度
的字符串常量,该长度仅受系统中允许DTrace 使用的内存数量的限制。结尾的空字节(\0)
由D编译器自动添加到所声明的任何字符串常量中。字符串常量对象的大小是与字符串关
联的字节数加上表示结尾空字节的一个附加字节。
字符串常量可能不含字面值换行符。要创建包含换行符的字符串,请使用\n 转义序列代替
字面值换行符。字符串常量也可包含任何为表2–5中的字符常量定义的特殊字符转义序
列。
字符串赋值
与char * 变量的赋值不同,字符串是按值而不是按引用复制。字符串赋值使用= 运算符进
行,从源操作数中复制实际的字符串字节,直到包含变量左侧的空字节为止,该变量必须
属于string 类型。可以通过对变量分配string 类型的表达式,来新建string 类型的变量。
例如,以下D语句:
s = "hello";
将新建一个string 类型的变量s,并将六个字节的字符串"hello" 复制到该变量中(五个可
列显字符和一个空字节)。字符串赋值与C 库函数strcpy(3C) 类似,不同之处在于,如果
源字符串超出目标字符串的存储空间限制,则结果字符串将自动按此限制截断。
也可以为字符串变量分配与字符串类型兼容的表达式。在此情况下,D编译器将自动提升
源表达式为字符串类型,然后执行字符串赋值。D 编译器允许将char * 类型或char[n] 类
型(即,任意大小的char 类型的标量数组)的任意表达式提升为string 类型。
字符串转换
使用强制类型转换表达式或应用stringof 特殊运算符,都可以将其他类型的表达式显式转
换为string 类型,其含义等价:
s = (string) expression s = stringof ( expression )
stringof 运算符紧密绑定到右侧的操作数。为了表达清晰,通常使用括号将表达式括起
来,但并非严格要求这样做。
字符串常量
86 Solaris 动态跟踪指南• 2006 年7 月
任何标量类型的表达式(如指针、整数、或标量数组地址)都可以转换为字符串。其他类
型的表达式(如void)不能转换为string。如果将无效地址错误地转换为字符串,DTrace
安全功能将会阻止破坏系统或DTrace,但您可能最后会跟踪一系列不可识别的字符。
字符串比较
D过载二元关系运算符,并允许使用这些运算符进行字符串比较和整数比较。只要两个操
作数均属于string 类型,或一个操作数属于string 类型而另一个操作数可以提升为string
类型,则关系运算符就可以进行字符串比较,如第86 页中的“字符串赋值”中所述。所有
关系运算符均可用于比较字符串:
表6–1 用于字符串的D关系运算符
< 左边的操作数小于右边的操作数
<= 左边的操作数小于或等于右边的操作数
> 左边的操作数大于右边的操作数
>= 左边的操作数大于或等于右边的操作数
== 左边的操作数等于右边的操作数
!= 左边的操作数不等于右边的操作数
与整数一样,每个运算符计算为int 类型的值,如果条件为true,则该值等于1,如果条件
为false,则等于0。
关系运算符逐字节比较两个输入字符串,这与C 库例程strcmp(3C) 类似。每个字节使用
ASCII 字符集中的相应整数值进行比较,如ascii(5) 中所示,直到读取空字节或达到最大字
符串长度为止。以下是一些D字符串比较及其结果的示例:
"coffee" < "espresso" ... 返回1 (true)
"coffee" == "coffee" ... 返回1 (true)
"coffee" >= "mocha" ... 返回0 (false)
阅读(426) | 评论(0) | 转发(0) |