6

我最近在阅读这篇关于 D 中的结构和类的文章,作者曾评论说

...这是结构的完美候选者。原因是它只包含一个成员,一个指向 ALLEGRO_CONFIG 的指针。这意味着我可以毫无顾忌地按值传递它,因为它只是指针的大小。

这让我思考;真的是这样吗?我可以想到一些情况,在这些情况下,相信你正在“免费”传递一个结构可能会有一些隐藏的陷阱。

考虑以下代码:

struct S
{
    int* pointer;
}

void doStuff(S ptrStruct)
{
    // Some code here
}

int n = 123;
auto s = S(&n);
doStuff(s);

当 s 被传递给 doStuff() 时,单个指针(包装在结构中)真的是传递给函数的全部吗?在我的脑海中,似乎任何指向成员函数的指针以及结构的类型信息也会被传递。

当然,这不会是类的问题,因为它们始终是引用类型,但是结构的按值传递语义向我表明,如上所述的任何额外“隐藏”数据都将与函数一起传递给函数struct 指向 int 的指针。这可能导致程序员认为他们正在传递一个(假设是 64 位机器)8 字节指针,而实际上他们正在传递一个 8 字节指针,以及其他几个指向函数的 8 字节指针,加上对象的类型信息有多少字节。然后,粗心的程序员在堆栈上分配的数据比预期的要多得多。

我是在这里追逐阴影,还是在传递带有单个引用的结构时这是一个有效的问题,并认为您正在获得一个伪引用类型的结构?D 中是否有某种机制可以防止这种情况发生?

4

2 回答 2

6

我认为这个问题可以概括为包装原生类型。例如,您可以创建一个 SafeInt 类型,它包装并像 int 一样工作,但会在任何整数溢出条件下抛出。

这里有两个问题:

  1. 编译器可能无法像使用本机类型一样优化您的代码。

    例如,如果您包装一个 int,您可能会实现重载的算术运算符。一个足够聪明的编译器将内联这些方法,并且生成的代码与使用 int 的代码没有什么不同。在您的示例中,愚蠢的编译器可能会以某种笨拙的方式编译取消引用(例如,获取结构开始的地址,添加指针字段的偏移量(即 0),然后取消引用)。

    此外,在调用函数时,编译器可能会决定以其他方式传递结构(例如由于优化不佳或 ABI 限制)。例如,如果编译器不注意结构的大小,并且以相同的方式处理所有结构,则可能会发生这种情况。

  2. struct如果你在函数中声明它,D 中的类型可能确实有一个隐藏成员。

    例如,以下代码有效:

    import std.stdio;
    
    void main()
    {
        string str = "I am on the stack of main()";
    
        struct S
        {
            string toString() const { return str; }
        }
    
        S s;
        writeln(s);
    }
    

    它之所以有效,是因为 S 保存了一个指向 main() 堆栈帧的隐藏指针。您可以通过在声明前加上前缀来强制结构没有任何隐藏指针static(例如static struct S)。

于 2012-11-10T06:27:54.120 回答
5

没有隐藏数据被传递。Astruct完全由其中声明的内容(以及必要时的任何填充字节)组成,仅此而已。不需要传递类型信息和成员函数信息,因为它们都是静态的。由于 astruct不能从 another 继承struct,因此不存在多态性。

于 2012-11-10T06:22:40.977 回答