news 2026/9/19 14:52:45

Slang 指针类型(Pointer Types)全面指南:声明语法、泛型指针、指针运算与并发测试验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Slang 指针类型(Pointer Types)全面指南:声明语法、泛型指针、指针运算与并发测试验证

Slang 指针类型(Pointer Types)全面指南:声明语法、泛型指针、指针运算与并发测试验证

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

导读

本文以 Slang 语言参考文档 types-pointer.md 为骨架,系统讲解 Slang 中指针类型的声明语法(T*)、标准库泛型指针(Ptr<T>/ImmutablePtr<T>)、地址取址与解引用、指针算术、指针 traits(访问模式与地址空间)以及当前的实现限制。文中同时引入 docs/generated/tests/conformance/types-pointer 下的一组一致性测试(conformance tests),以真实的.slang测试用例印证文档中的每一条声明。读者读完本文后,将能准确写出可编译、可运行的 Slang 指针代码,理解指针在 CPU/CUDA 目标上的行为边界,并能够看懂仓库中的指针相关测试是如何组织与断言(CHECK)的。

注意:Slang 文档明确标注指针在slangc中尚未完全实现("Pointers are not yet fully implemented inslangc"),因此本文所述内容以当前仓库文档与测试所确认的范围为准。

声明语法(Declaration Syntax):T*

语法形式与类型语义

指针类型通过在类型说明符后附加'*'声明。语言参考中给出的类型说明符语法为:

simple-type-id-spec = [modifier-list] type-identifier [generic-params-decl] ( '[' [constant-index-expr] ']' | '*' )*

其中:

  • modifier-list:可选的修饰符列表;
  • type-identifier:命名现有类型或泛型类型的标识符,可以是基础类型、向量/矩阵泛型类型、用户自定义的结构体类型、接口类型、枚举类型、类型别名或模块提供的类型;
  • generic-params-decl:泛型参数声明;
  • '[' constant-index-expr ']':数组维度声明;
  • '*':指针声明,即本主题。

语义要点:

  • 指针类型T*表示"指向类型T的对象"的地址。
  • 基类型可以是任何 addressable(可寻址)类型,包括指针类型与数组类型(即支持"指向指针的指针"、"指向数组的指针"等嵌套形式)。
  • Remark 1(等价关系):声明语法写出的指针类型等价于泛型指针类型Ptr<T, Access.ReadWrite, AddressSpace.Device>
  • Remark 2:指针也可以通过变量声明来声明——此时变量本身被声明为指针,而非类型本身是指针类型。

测试验证:T*声明、取址与解引用往返

一致性测试 declare-star-syntax-functional.slang 以INTERPRET(解释执行)模式验证声明、取址、解引用三者协同工作:

//TEST:INTERPRET(filecheck=CHECK): void main() { int x = 42; int* p = &x; //CHECK: 42 printf("%d\n", *p); }

测试通过filecheck=CHECK断言*p的输出为42,证明int* p = &x完整覆盖了"声明指针类型 → 取址 → 解引用"的往返链路。

T*Ptr<T, Access.ReadWrite, AddressSpace.Device>类型等价

测试 ptr-star-equals-ptr-generic-functional.slang 进一步验证 Remark 1 的类型等价性——T*值可以直接赋值给全限定形式的Ptr<>变量,无需任何转换:

//TEST:INTERPRET(filecheck=CHECK): void main() { int x = 42; int* p1 = &x; Ptr<int, Access.ReadWrite, AddressSpace.Device> p2 = p1; //CHECK: 42 42 printf("%d %d\n", *p1, *p2); }

两个指针解引用得到相同结果,印证两种拼写是同一类型。

泛型指针类型(Generic Pointer Types)

Slang 标准库提供两种泛型指针类型别名:

别名语义参数
Ptr<T, AccessMode, AddressSpace>通用指针类型T:元素类型;AccessMode:存储访问模式;AddressSpace:存储地址空间
ImmutablePtr<T, AddressSpace>指向不可变数据的指针类型别名T:元素类型;AddressSpace:存储地址空间

关于AccessModeAddressSpace的取值详见下文 指针 traits 一节。

Ptr<T>的声明与解引用

测试 ptr-generic-type-functional.slang 验证Ptr<T>别名可声明、可从&obj赋值并可解引用:

//TEST:INTERPRET(filecheck=CHECK): void main() { int x = 55; Ptr<int> p = &x; //CHECK: 55 printf("%d\n", *p); }

ImmutablePtr<T>只读指针

测试 immutableptr-type-functional.slang 验证只读别名:由于没有隐式转换(见 指针 traits),需要通过显式 cast 从T*得到ImmutablePtr<int>,随后可以正常解引用读取:

//TEST:INTERPRET(filecheck=CHECK): void main() { int x = 33; ImmutablePtr<int> ro = (ImmutablePtr<int>)(&x); //CHECK: 33 printf("%d\n", *ro); }

描述(Description):取址、解引用与指针算术

地址获取:&__getAddress

取址运算符&用于获取对象的地址,可以赋给类型匹配的指针变量;标准库内建函数__getAddress(obj)&的替代写法。

  • 测试 address-of-operator-functional.slang 验证&a产生可用指针、解引用还原原值(*(&obj) == obj)。
  • 测试 getaddress-builtin-functional.slang 验证__getAddress(obj)产生同样的有效指针。

解引用:读与写

解引用运算符*访问(读取)被指向的对象,并且*pl-value——通过*p = v可以改变被指向对象的值。

测试 dereference-write-functional.slang 验证写操作对原变量可见:

//TEST:INTERPRET(filecheck=CHECK): void main() { int x = 10; int* p = &x; *p = 99; //CHECK: 99 printf("%d\n", x); }

指针作为函数参数传入时,被调函数可通过解引用修改调用方变量。测试 pointer-pass-to-function-functional.slang 验证了这一点:

void addDelta(int* p, int delta) { *p = *p + delta; } void main() { int x = 10; addDelta(&x, 5); //CHECK: 15 printf("%d\n", x); }

成员访问:p->member

当指针指向结构体或类类型时,可以使用成员访问运算符.->解引用并访问成员。测试 struct-arrow-member-access-functional.slang 验证p->member(*p).member访问同一成员,且通过->的修改对原对象可见(C10)。

指针算术:+-++--

当指针指向数组元素时,可以对指针加/减整数值,得到的指针指向偏移后的元素:

  • p + n(C11):向前移动n个元素。测试 pointer-arithmetic-add-functional.slang 验证解引用结果正确;pointer-arithmetic-spirv-emission.slang 验证发射(emission)形态(见下文)。
  • p - n(C12):向后移动n个元素。测试 pointer-arithmetic-sub-functional.slang 验证。
  • ++p/--p(C13):前进/后退一个元素。测试 pointer-increment-functional.slang 验证。
各后端指针算术的发射形态

pointer-arithmetic-spirv-emission.slang 使用运行时偏移(从 buffer 读取偏移量以防止常量折叠),验证各后端的 lowering 结果:

//TEST:SIMPLE(filecheck=CHECK):-target spirv-asm -entry main -stage compute //TEST:SIMPLE(filecheck=CHECKCPP):-target cpp -entry main -stage compute //TEST:SIMPLE(filecheck=CHECKCUDA):-target cuda -entry main -stage compute [numthreads(1,1,1)] void main(RWStructuredBuffer<int> inputBuf, RWStructuredBuffer<int> outputBuf) { int arr[4]; arr[0] = inputBuf[0]; arr[1] = inputBuf[1]; arr[2] = inputBuf[2]; arr[3] = inputBuf[3]; int* p = &arr[0]; p = p + inputBuf[4]; // runtime offset — forces OpPtrAccessChain outputBuf[0] = *p; } // CHECK: OpPtrAccessChain // CHECKCPP: &arr_0[int(0)] + // CHECKCUDA: &arr_0[int(0)] +

关键断言:

  • SPIR-V:本地数组上的运行时指针算术发射为OpPtrAccessChain
  • CPP / CUDA:发射为显式的指针加法表达式(&arr_0[int(0)] + ...)。

空指针与"末尾之后"哨兵

  • 空指针(C14)nullptr是合法的空指针值,p == nullptr对空指针为真、对非空指针为假。测试 null-pointer-functional.slang 验证。
  • 末尾之后哨兵(C15)&arr[N-1] + 1是合法的"数组最后一个元素之后"的指针,可作为哨兵值参与比较,但不可解引用

语言参考还给出了一段指针算术合法性示例(Remark 2):

var arr : uint[10] = { }; var ptr : uint *; ptr = &arr[9]; // OK: ptr points to the last element // of the array ptr++; // Still OK: ptr points to one past the // last element ptr++; // Pointer is now invalid ptr--; // No validity guarantees with invalid // pointers in pointer expressions; // dereferencing would be undefined behavior

指针值的四类归属与未定义行为

文档将指针值划分为四类:

  1. 指向具有匹配 traits 的对象的指针(包括指向数组元素的指针);
  2. 指向对象末尾之后的指针;
  3. 空指针(指向任何对象的特殊指针值);
  4. 无效指针(其余情况)。

解引用不指向"具有匹配 traits 的对象"的指针是未定义行为(undefined behavior)。另外文档特别警告:当指针指向多维数组中的元素时,指针算术必须始终停留在同一最内层数组中,或得到"指向该数组最后一个对象之后"的指针(该指针不可解引用),其他任何结果都是未定义行为。

指针 Traits(Pointer Traits)

指针类型具有以下 traits:

  • 被指向对象的类型;
  • 访问模式(access mode);
  • 地址空间(address space)。

一个有效指针只能指向 traits 匹配的对象。

默认值:Device 地址空间 + ReadWrite 访问模式

默认指针地址空间是AddressSpace.Device,默认访问模式是Access.ReadWrite;使用声明语法无法指定其他地址空间或访问模式。测试 pointer-default-access-mode-functional.slang 验证了这一默认行为(C16)。

其他地址空间与访问模式的指针,需通过标准库类型别名Ptr<T, AccessMode, AddressSpace>声明。

禁止隐式转换:读写 → 只读

不存在从读写指针(Ptr<T>)到只读指针(ImmutablePtr<T>)的隐式转换。测试 no-implicit-rw-to-immutable-negative.slang 以负向诊断测试(DIAGNOSTIC_TEST)断言编译器以错误码E30019拒绝隐式赋值:

//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive):-target cpp void main() { int x = 10; Ptr<int> rw = &x; ImmutablePtr<int> ro = rw; //CHECK: E30019 }

如需只读指针,必须使用显式 cast(参见上文ImmutablePtr<T>一节)。

当前限制与注意点

文档明确列出了指针尚未完全实现的范围,一致性测试也对此进行了验证与记录:

  1. 局部内存指针仅支持 CPU 与 CUDA 目标operator&在核心模块中被门控在cpp_cuda_spirv_llvm能力集上;对 Vulkan/D3D12 等 GPU 目标编译包含本地内存取址的着色器会以error[E36107]: unavailable features in entry point中止(需 GPU 驱动环境才能验证,CI 会覆盖)。
  2. 不支持指向不透明句柄类型(如Texture2D)的指针;句柄指针应使用DescriptorHandle<T>。测试中发现一个文档缺口(doc gap):对全局作用域的Texture2D取址实际报错为 E30079("cannot take address of immutable object"),而不是专门的"不支持不透明句柄指针"诊断——原因是资源类型是隐式不可变对象。详见一致性测试 README 的 "Doc gaps observed" 一节。
  3. 不支持const指针const T*声明被编译器以错误码E20017拒绝,负向测试 const-pointer-rejected-negative.slang 固定了该诊断;文档建议改用Ptr<T, Access.Read>let/const Ptr<...>表达只读语义。
  4. 不支持自定义对齐规格;对于已知对齐的指针加载/存储,应使用标准库函数 loadAligned() /storeAligned()(这些函数定义在本文档之外的标准库中)。
  5. 并非所有目标都支持指针。发射测试 declare-star-syntax-emission.slang 展示了各后端的实际表现:
    • CPP:发射int32_t等原生指针类型(CHECK: int32_t);
    • CUDA:发射__global__内核标记;
    • SPIR-V asm:发射OpTypePointer StorageBuffer(存储缓冲指针类型);
    • Metal:设备内存指针发射为int device* devPtr
    • GLSL:指针以GL_EXT_buffer_reference块形式渲染(layout(buffer_reference, std430, buffer_reference_align = 4) buffer BufferPointer_...);
    • HLSL / WGSL被排除在发射 fan-out 之外:D3D 没有指针类型,slangc会把 Slang 内部的Ptr<int, ...>泛型泄漏到 HLSL 输出中;WGSL 的ptr<>要求地址空间,而当前输出中地址空间为空。这两项分别记录为待发现项(findings)_meta/findings/hlsl-pointer-param-emits-internal-ptr-generic.yaml_meta/findings/wgsl-pointer-param-emits-empty-address-space.yaml
  6. 不支持指针继承。指向符合接口I的结构体的指针不能 cast 为指向I的指针。测试记录了一个文档缺口:编译器在 parse/check 阶段接受(IFoo*)bp这种显式 cast,失败只会在 code-gen 阶段尝试接口动态派发时以E50100出现——即被拒绝的是派发而非 cast 本身。

实战示例:用指针表示半开区间[start, end)

语言参考的 Examples 一节演示了指针最典型的实战用法——用一对指针表示半开区间并求和。以下完整示例可直接放入计算着色器:

RWStructuredBuffer<uint> outputBuffer; cbuffer Globals { Ptr<uint> g_inputData; uint g_inputDataLen; } // Calculate sum of half-open range [start, end) uint sumOfValues(uint *start, uint *end) { uint sum = 0; for (uint *i = start; i != end; ++i) { sum = sum + *i; } return sum; } [numthreads(1, 1, 1)] void main(uint3 id : SV_DispatchThreadID) { // Calculate sum of elements 0, 1, ..., 9 provided // the input data buffer is big enough. outputBuffer[id.x] += sumOfValues( g_inputData, &g_inputData[min(g_inputDataLen, 10)]); }

这段代码同时覆盖了多项核心能力:

  • Ptr<uint> g_inputData使用泛型指针别名声明 uniform 数据指针;
  • uint *start, *end使用声明语法;
  • 通过&g_inputData[...]取址构造区间终点;
  • 循环条件i != end依赖"末尾之后哨兵"的合法比较;
  • ++i使用指针自增;
  • *i解引用读取元素。

一致性测试 pointer-range-iteration-functional.slang 以解释执行方式验证了相同的区间迭代模式——对数组{1,2,3,4,5}end = &arr[4] + 1为哨兵求和,CHECK 断言结果为15

uint sumRange(uint* start, uint* end) { uint sum = 0; for (uint* i = start; i != end; ++i) sum = sum + *i; return sum; } void main() { uint arr[5] = { 1, 2, 3, 4, 5 }; uint* start = &arr[0]; uint* end = &arr[4] + 1; // one past last — valid sentinel per doc uint result = sumRange(start, end); //CHECK: 15 printf("%d\n", result); }

一致性测试套件总览:18 项声明的验证矩阵

conformance/types-pointer 目录 下的测试套件把语言参考中的指针声明逐条映射为可执行测试(claims 编号 C1–C18),验证方式分为三类:

  • functional:功能验证,通常以TEST:INTERPRET(filecheck=CHECK)解释执行并断言输出;
  • negative:负向诊断验证,以DIAGNOSTIC_TEST固定错误码(E20017、E30019、E30079、E50100 等);
  • boundary:边界行为验证(如空指针、单元素步进、区间哨兵)。
声明(Claim)意图对应测试文件
C1.T*声明 +&/*往返正确functionaldeclare-star-syntax-functional.slangdeclare-star-syntax-emission.slang
C2.T*Ptr<T, Access.ReadWrite, AddressSpace.Device>同类型functionalptr-star-equals-ptr-generic-functional.slangpointer-default-access-mode-functional.slang
C3.const T*被 E20017 拒绝negativeconst-pointer-rejected-negative.slang
C4.Ptr<T>泛型可声明、可解引用functionalptr-generic-type-functional.slang
C5.ImmutablePtr<T>别名经显式 cast 可编译解引用functionalimmutableptr-type-functional.slang
C6.&obj产生有效指针,*(&obj) == objfunctionaladdress-of-operator-functional.slang
C7.__getAddress(obj)&的替代functionalgetaddress-builtin-functional.slang
C8/C9.*p可读,*p = v可写functionaldereference-write-functional.slang
C10.p->member(*p).member等价functionalstruct-arrow-member-access-functional.slang
C11.p + n前进 n 元素functionalpointer-arithmetic-add-functional.slangpointer-arithmetic-spirv-emission.slang
C12.p - n后退 n 元素boundarypointer-arithmetic-sub-functional.slang
C13.++p/--p单元素步进boundarypointer-increment-functional.slang
C14.nullptr空指针比较boundarynull-pointer-functional.slang
C15/C18. 末尾之后哨兵 + 区间迭代求和正确functionalpointer-range-iteration-functional.slang
C16. 默认地址空间 Device、默认访问模式 ReadWritefunctionalpointer-default-access-mode-functional.slang
C17. 读写 → 只读无隐式转换(E30019)negativeno-implicit-rw-to-immutable-negative.slang
指针作函数参数可修改调用方变量functionalpointer-pass-to-function-functional.slang

未被测试覆盖的声明(Untested claims)

README 还诚实记录了当前无法通过自动化测试覆盖的声明及其原因,主要包括:

  • 局部内存指针仅 CUDA/CPU 支持:验证 GPU 目标拒绝本地内存指针需要真实 GPU 驱动(Vulkan/D3D12),由 CI 覆盖;
  • 不支持不透明句柄指针:实际错误是 E30079(关于不可变性)而非专门的"不透明句柄"诊断,属文档措辞不精确;
  • 对齐加载/存储loadAligned()/storeAligned()定义在本文档之外,无法从声明语法表面直接测试;
  • 指针到接口的继承 cast:编译器接受显式 cast,仅当 code-gen 阶段尝试接口派发时才以 E50100 失败;
  • 未定义行为类声明(解引用无效指针、多维数组越界算术):UB 本质上是规范性的,编译器无需拒绝或标记,按定义不可测试;
  • 非 Device 地址空间(如AddressSpace.GroupShared):需要 GPU 运行环境与特定能力集;
  • HLSL/WGSL 发射:当前两个后端会泄漏内部泛型或发射空地址空间,属已知缺陷(findings)。

文档缺口记录(Doc gaps)

测试作者在验证过程中发现了两处文档措辞与实际行为不一致之处,均记录在 README 的 "Doc gaps observed" 一节:

  1. "不支持不透明句柄指针"表述模糊&myTex(全局Texture2D)实际报 E30079,读者无法区分错误源于"不透明性"还是"全局不可变性";建议文档说明不透明资源类型是隐式不可变对象,或新增专门诊断。
  2. "不能 cast 到指向接口的指针"表述不准确:cast 在 parse/check 阶段被接受,失败发生在 code-gen 的接口派发阶段(E50100);建议文档澄清被拒绝的是动态派发而非 cast 本身。

这两处记录对读者极具价值:它们精确划定了文档语义与编译器实现之间的边界。

小结

Slang 的指针类型以T*声明语法为入口,以Ptr<T, AccessMode, AddressSpace>ImmutablePtr<T, AddressSpace>两个标准库别名扩展了访问模式与地址空间的表达能力。取址(&/__getAddress)、解引用(*,l-value 可写)、成员访问(->)、指针算术(+/-/++/--)、空指针与末尾之后哨兵构成了完整的指针语义;默认AddressSpace.Device+Access.ReadWrite的 traits 规则与"读写 → 只读无隐式转换"(E30019)是类型安全的关键约束。由于slangc尚未完全实现指针,实际使用时务必牢记:局部内存指针仅限 CPU/CUDA,const T*会被 E20017 拒绝,不透明句柄请使用DescriptorHandle<T>,HLSL/WGSL 目标暂不可用。仓库中的一致性测试套件(docs/generated/tests/conformance/types-pointer)为文档的每一项声明提供了可执行、可断言的验证,是理解与排查指针行为的首选参考资料。

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 14:52:13

STM32定时器完全攻略:从硬件原理到HAL库实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 14:50:54

同花顺公式编程入门:从指标编写到条件选股与参数优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 14:45:19

塑料检测标准体系与实操方法:从ISO/ASTM到数据追溯全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 14:41:51

大工物理期中卷:电磁学计算模型与Python验证全解析

简介&#xff1a;大连理工大学大学物理期中复习题&#xff0c;围绕电学与磁学两大板块&#xff0c;适合正在备考期中考试的学生巩固基础。资源为一份PDF&#xff0c;压缩包内共1个文件、总大小584KB&#xff0c;内容紧凑&#xff0c;覆盖电容器串联并联能量比、电场强度与电势关…

作者头像 李华
网站建设 2026/9/19 14:40:32

游戏汉化工具汇总:从引擎识别到文本回填的完整流程

直接开写。汉化游戏这事儿&#xff0c;我前前后后折腾了得有七八年。从最早在论坛蹲汉化补丁&#xff0c;到自己动手拆包、抽文本、翻译、回封、修字体&#xff0c;算是一路踩坑踩过来的。最近整理硬盘&#xff0c;发现自己随手存了二十多款免费工具&#xff0c;正好借这篇文做…

作者头像 李华