d同c对接数组与函数

1002 篇文章 1 订阅
48 篇文章 0 订阅

原地址
作者: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)构嵌套在函数中时,才有环境指针.而嵌套在构,联,类中,则无环境针.

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值