d同c对接数组与函数
原地址
作者:m.p
.
本篇讨论声明和调用数组
为参数的函数
.d
中用c库
很简单,但细节差异
很重要.
c
中声明接收数组
参数的函数.
void f0(int *arr);
c
中可传int a[]/int b[3]
给函数,然后数组退化
为指针
.即传了个指针
给函数.类似函数中,得给出结束
标记,如串中\0
结束标志,或者参数中给出指针,长度
.
void f1(int *arr, size_t len);
但,并不完美.如果f0
无结束标记,f1
长度比实际长度小,则会损坏内存.d
已经安全的解决了,但d
调用c
则可能会崩溃
内存.
c
中还有其他声明方法:
void f2(int arr[]);
void f3(int arr[9]);
void f4(int arr[static 9]);
其实他们与f0
一样,都要退化
为指针.第f3
中的9
没啥意义.f4
表明至少有9
个元素,禁止了空指针
.但得看编译器.
d
不关心c编译器
行为,只关心如何声明
它,这样,不会崩溃/未期望
结果,我们可以这样:
extern(C):
void f0(int* arr);
void f1(int* arr, size_t len);
void f2(int* arr);
void f3(int* arr);
void f4(int* arr);
我们可以
,不代表我们应该
,我们这样:
extern(C):
void f2(int[] arr);
void f3(int[9] arr);
void f4(int[9] arr);
这样做有后果
吗?是的,但不意味着改成整*
版.我们要理解d数组
本质.
d
区分动态/静态数组
.
int[] a0;
int[9] a1;
a0动态,a1静态
,都有.针/.长度
属性.都可用[]
索引.关键区别是:
动态数组
一般(并不是全部)在堆上
分配.上面,未给a0
分配内存.它需要用新/分配
来初化.此时因为未初化,a0.ptr是null而a0.length是0
.
动态数组是聚集
,包含两个属性:
struct DynamicArray {
size_t length;
size_t ptr;
}
动态数组本质是引用
.用指针/长度对
作为元素
句柄.内置
类型都有.sizeof
,我们取a0
的大小
,就会发现在32位
下为8
,64位
为16
.因为size_t
大小跟随系统而变.它是句柄
大小,而不是元素大小
.
静态数组,在栈中
分配.默认都初化为初值
,在此为0
.静态数组是值类型
.值为所有元素
.所以其大小为4*9
.其长度
元素不变.而针
亦不变,甚至不是左值
,即,你不能赋值给他
.c
的数组
都是静态数组
.所以在转换声明
时要小心
.
现在实现f2
:
void f2(int arr[]) {
for(int i=0; i<3; ++i)
printf("%d\n", arr[i]);
}
d
中'简单'
声明:
extern(C) void f2(int[]);
void main() {
int[] a = [10, 20, 30];
f2(a);
}
这是不对
的.cl /c f2.c|dmd -m64 df2.d f2.obj
.然后运行df2
.得到:
3
0
1970470928
编译没错,外(c)
表明是c
调用传统.调用约定
影响函数传递方式及函数混杂方式
.其他stdcall用extern(Windows)
有自己的混杂机制
.上面的符号可能为_f2/f2
.如果其他语言要调用d
函数,就标记d
函数为外(c)
.
链接也没问题.因为类型/参数个数未混杂进符号名
.c
函数期望指针
,但却接收到长度+指针
.因而意思是,c
函数期望的是指针
.因而d中声明
要改为指针
.
extern(C) void f2(int*);
void main() {
int[] a = [10, 20, 30];
f2(a.ptr);
}
这样改后,再编译.注意a.针
.需要指针
时,传递d数组是
不行的.当然有特例(串字面量
).所以必须用.针
.
f3,f4
类似:
void f3(int arr[9]);
void f4(int arr[static 9]);
注意整[9]
在d
中是静态数组,以下不匹配c声明
void f3(int[9]);
void f4(int[9]);
c实现:
void f3(int arr[9]) {
for(int i=0; i<9; ++i)
printf("%d\n", arr[i]);
}
d实现:
extern(C) void f3(int[9]);
void main() {
int[9] a = [10, 20, 30, 40, 50, 60, 70, 80, 90];
f3(a);
}
这可能会崩溃,取决于系统
,这是按值传递九个数组元素
而不是传指针
.
考虑:
typedef float[16] mat4f;
void do_stuff(mat4f mat);
绑定c
库时,最好保持与c
一样的接口
.但如果翻译成:
alias mat4f = float[16];
extern(C) void do_stuff(mat4f);
则,每次调用都要传递16个浮
.对所有用mat4f
的函数都这样.一种方法是像int[]
样,声明带个指针
函数.但这样,就可能出现传递<16
长度的数组.但c
端还可以带个长度
参数.但一般像mat4f
这种,又没有长度
参数.
但d
可以更好.
void do_stuff(ref mat4f);
可保证
是静态数组,且为16
长度.且引用
为指针.完全按c端
要求运行.现在重写.
extern(C) void f3(ref int[9]);
void main() {
int[9] a = [10, 20, 30, 40, 50, 60, 70, 80, 90];
f3(a);
}
大多数时候,对接c
时,可直接复制粘贴
.但对特例
要小心,上篇,编译器可抓声明数组错误
,而c
函数声明,则不一定,所以d
中同c
对接要像写c
代码一样小心.
下篇讲混合c串
和d串
.d的切片是篇好文章.
仅当非旧类型(pod)构
嵌套在函数
中时,才有环境指针
.而构
嵌套在构,联,类
中,则无环境针
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现