为了账号安全,请及时绑定邮箱和手机立即绑定

在 C 和 Go 中传递指针而不是返回新变量?

在 C 和 Go 中传递指针而不是返回新变量?

Go
哆啦的时光机 2022-10-17 16:49:14
为什么在 C 和 Go 中约定将指针传递给变量并更改它而不是返回带有值的新变量?在 C 中:#include <stdio.h>int getValueUsingReturn() {    int value = 42;    return value;}void getValueUsingPointer(int* value ) {    *value = 42;}int main(void) {  int valueUsingReturn = getValueUsingReturn();  printf("%d\n", valueUsingReturn);  int valueUsingPointer;  getValueUsingPointer(&valueUsingPointer);  printf("%d\n", valueUsingPointer);  return 0;}在围棋中:package mainimport "fmt"func getValueUsingReturn() int {    value := 42    return value}func getValueUsingPointer(value *int) {    *value = 42}func main() {    valueUsingReturn := getValueUsingReturn()    fmt.Printf("%d\n", valueUsingReturn)    var valueUsingPointer int    getValueUsingPointer(&valueUsingPointer)    fmt.Printf("%d\n", valueUsingPointer)}做其中一项是否有任何性能优势或限制?
查看完整描述

2 回答

?
富国沪深

TA贡献1790条经验 获得超9个赞

首先,我对 Go 的了解不够,无法对其做出判断,但答案将适用于 C 的情况。


如果您只是在研究像ints 这样的原始类型,那么我会说这两种技术之间没有性能差异。


当structs 发挥作用时,通过指针修改变量有一个非常小的优势(纯粹基于您在代码中所做的事情)


#include <stdio.h>


struct Person {

    int age;

    const char *name;

    const char *address;

    const char *occupation;

};


struct Person getReturnedPerson() {

    struct Person thePerson = {26, "Chad", "123 Someplace St.", "Software Engineer"};

    return thePerson;

}


void changeExistingPerson(struct Person *thePerson) {

    thePerson->age = 26;

    thePerson->name = "Chad";

    thePerson->address = "123 Someplace St.";

    thePerson->occupation = "Software Engineer";

}


int main(void) {

  struct Person someGuy = getReturnedPerson();

  


  struct Person theSameDude;

  changeExistingPerson(&theSameDude);

  

  

  return 0;

}

GCC x86-64 11.2


没有优化


通过函数的 return返回struct变量比较慢,因为必须通过分配所需的值来“构建”变量,然后将变量复制到返回值。


当您通过指针间接修改变量时,除了将所需的值写入内存地址(基于您传入的指针)之外,别无他法


.LC0:

        .string "Chad"

.LC1:

        .string "123 Someplace St."

.LC2:

        .string "Software Engineer"

getReturnedPerson:

        push    rbp

        mov     rbp, rsp

        mov     QWORD PTR [rbp-40], rdi

        mov     DWORD PTR [rbp-32], 26

        mov     QWORD PTR [rbp-24], OFFSET FLAT:.LC0

        mov     QWORD PTR [rbp-16], OFFSET FLAT:.LC1

        mov     QWORD PTR [rbp-8], OFFSET FLAT:.LC2

        mov     rcx, QWORD PTR [rbp-40]

        mov     rax, QWORD PTR [rbp-32]

        mov     rdx, QWORD PTR [rbp-24]

        mov     QWORD PTR [rcx], rax

        mov     QWORD PTR [rcx+8], rdx

        mov     rax, QWORD PTR [rbp-16]

        mov     rdx, QWORD PTR [rbp-8]

        mov     QWORD PTR [rcx+16], rax

        mov     QWORD PTR [rcx+24], rdx

        mov     rax, QWORD PTR [rbp-40]

        pop     rbp

        ret

changeExistingPerson:

        push    rbp

        mov     rbp, rsp

        mov     QWORD PTR [rbp-8], rdi

        mov     rax, QWORD PTR [rbp-8]

        mov     DWORD PTR [rax], 26

        mov     rax, QWORD PTR [rbp-8]

        mov     QWORD PTR [rax+8], OFFSET FLAT:.LC0

        mov     rax, QWORD PTR [rbp-8]

        mov     QWORD PTR [rax+16], OFFSET FLAT:.LC1

        mov     rax, QWORD PTR [rbp-8]

        mov     QWORD PTR [rax+24], OFFSET FLAT:.LC2

        nop

        pop     rbp

        ret

main:

        push    rbp

        mov     rbp, rsp

        sub     rsp, 64

        lea     rax, [rbp-32]

        mov     rdi, rax

        mov     eax, 0

        call    getReturnedPerson

        lea     rax, [rbp-64]

        mov     rdi, rax

        call    changeExistingPerson

        mov     eax, 0

        leave

        ret

轻微优化


但是,今天的大多数编译器都可以弄清楚您在这里尝试做什么,并将平衡两种技术之间的性能。


如果你想绝对小气,传递指针仍然会稍微快几个时钟周期。


在从函数返回一个变量时,你至少还需要设置返回值的地址。


        mov     rax, rdi

但是在传递指针时,甚至没有这样做。


但除此之外,这两种技术没有性能差异。


.LC0:

        .string "Chad"

.LC1:

        .string "123 Someplace St."

.LC2:

        .string "Software Engineer"

getReturnedPerson:

        mov     rax, rdi

        mov     DWORD PTR [rdi], 26

        mov     QWORD PTR [rdi+8], OFFSET FLAT:.LC0

        mov     QWORD PTR [rdi+16], OFFSET FLAT:.LC1

        mov     QWORD PTR [rdi+24], OFFSET FLAT:.LC2

        ret

changeExistingPerson:

        mov     DWORD PTR [rdi], 26

        mov     QWORD PTR [rdi+8], OFFSET FLAT:.LC0

        mov     QWORD PTR [rdi+16], OFFSET FLAT:.LC1

        mov     QWORD PTR [rdi+24], OFFSET FLAT:.LC2

        ret

main:

        mov     eax, 0

        ret


查看完整回答
反对 回复 2022-10-17
?
繁华开满天机

TA贡献1816条经验 获得超4个赞

我认为对您的问题的简短回答(至少对于 C,我不熟悉 GO 内部)是 C 函数是按值传递的,通常也按值返回,因此必须复制数据对象,人们担心所有的性能复制。对于大型对象或深度复杂的对象(包含指向其他内容的指针),将被复制的值作为指针通常更有效或更合乎逻辑,因此函数可以“操作”数据而无需复制它. 话虽如此,现代编译器在确定参数数据是否适合寄存器或有效复制返回的结构等内容方面非常聪明。底线是现代 C 代码做最适合您的应用程序或对您来说最清楚的事情。如果至少在开始时会降低可读性,请避免过早优化。还有编译器资源管理器(https://godbolt.org/)是你的朋友,如果你想检查不同风格的效果,特别是在优化方面。



查看完整回答
反对 回复 2022-10-17
  • 2 回答
  • 0 关注
  • 99 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信