C11 中的输出参数或 return 结构?

Output parameter or return struct in C11?

我知道 C++11 具有移动语义,这意味着您可以直接从函数中 return 一个结构而不用担心它被复制(假设是一个简单的结构),而不是编写结构通过输出参数。

C11有这样的吗?还是 returned 结构每次都会被复制?输出参数还是这里的"best practice"吗?

我认为这里有些混乱需要澄清。 C++的语义实现是不同的。

  • "Move" 与 C++ 中的 "copy" 的区别只是调用哪个构造函数(或 operator=)的问题。

  • 结构成员的表示是否被复制的问题是一个完全不同的问题。换句话说,"does the processor have to move these bytes around?" 不是语言语义的一部分。

语义

MyClass func() {
    MyClass x;
    x.method(...);
    ...
    return x;
}

此 return 使用移动语义(如果可用),但即使在 C++11 之前,return 值优化也是可用的。

我们更喜欢使用移动语义的原因是因为移动对象不会导致深度复制,例如,如果您移动 std::vector<T>,则不必复制所有 T。但是,您仍在复制数据!所以,std::move(x)在语义上是一个移动操作(认为它是使用线性而不是经典逻辑)但它仍然是通过在内存中复制数据来实现的。

除非你的 ABI 让你避免复制。这将我们带到...

实施

当您调用一个 return 是一个大型结构的函数时(术语 "large" 是相对的,它可能只是几个词),大多数 ABI 将调用该结构以通过函数的引用。所以当你写这样的东西时:

MyClass func() { ... }

一旦你在汇编中查看它,它可能看起来更像这样:

void func(MyClass *ptr) { ... }

当然,这是一个简化!指针通常是隐式的。但重要的一点是,我们已经在避免复制,有时

案例研究

这是一个简单的例子:

struct big {
    int x[100];
};

struct big func1(void);

int func2() {
    struct big x = func1();
    struct big y = func1();
    return x.x[0] + y.x[0];
}

当我在 x64 上用 gcc -O2 编译它时,我得到以下汇编输出:

    subq    8, %rsp
    movq    %rsp, %rdi
    call    func1
    leaq    400(%rsp), %rdi
    call    func1
    movl    400(%rsp), %eax
    addl    (%rsp), %eax
    addq    8, %rsp
    ret

您可以看到无处struct big复制。 func2() 的结果简单地放在堆栈上等待 func1(),然后堆栈被移动,因此下一个结果被放在其他地方。

但是

  • 在常见的 ABI 中,大型函数结果不会通过多个函数堆栈进行线程化。从上面的 func2() 返回 xy 将导致结构被复制。

  • 但是记住!这与 移动语义 复制语义 无关,因为复制的结构数据只是一个实现细节,而不是语言语义。在 C++ 中,使用 std::move() 可能仍会导致结构被复制,只是不会调用复制构造函数。

结论:return从 C 或 C++ 构建大型结构可能会导致复制结构,取决于 ABI 的细节,函数如何已优化,并且有问题的代码。但是,如果结构只有几个字,我不会担心。