我发现复合文字是将初始化的数组和 struct 发送到函数而无需编写过多冗长代码的一种非常有用和优雅的方法,但我想了解以这种方式编写代码的成本,例如下面这个简单的胡说八道的程序:

#include <stdio.h>

struct st{
  int a;
  int b;
};

int foo(struct st bar){
  return bar.a * bar.b;
}

int main(void){
  printf("%d %d\n", foo((struct st){.a=4, .b=6}), foo((struct st){.a=7, .b=19})); 
}

毫无怨言地编译,并按预期输出24 133个.

我可以修改伪代码,使函数接受指向struct st的指针,而不是值:

#include <stdio.h>

struct st{
  int a;
  int b;
};

int foo(struct st *bar){
  return bar->a * bar->b;
}

int main(void){
  printf("%d %d\n", foo(&(struct st){.a=4, .b=6}), foo(&(struct st){.a=7, .b=19})); 
}

编译器不会介意,输出也不会改变.显然,我要求编译器做的事情在这些情况下是不同的.在第一个示例中,编译器可以注意到复合文字是通过值发送给函数的,因此在调用foo()之后,main()中的任何future 代码都完全无法到达通过值发送的复合文字.在我看来,从理论上讲,编译器可以注意到这一点,并占用相同的内存空间来满足对两个文字的请求,可能会像这样重新分析Main函数:

int main(void){
  struct st a = {4,6};
  int ret1 = foo(a);
  a.a = 7;
  a.b = 19;
  int ret2 = foo(a);
  printf("%d %d\n",ret1, ret2);
}

但在代码的第二个版本中,文字是通过引用发送的,因此编译器可能无法轻松推断他们的内存是可删除的,可能会这样解释代码:

int main(void){
  struct st a = {4,6};
  struct st b = {7,19};
  int ret1 = foo(&a);
  int ret2 = foo(&b);
  printf("%d %d\n",ret1, ret2);
}

在我的理解中,这迫使编译器分配更多的堆栈内存来完成相同的任务.

这是对正在发生的事情的准确判断吗?C标准是否允许编译器重用技术上仍在作用域内的内存?

如果不是,这是否意味着代码如下:

for(int c = 0; c < SOME_RUNTIME_VALUE; ++c)
    printf("%d\n", foo((struct st){.a = c/4, .b = c%4}));

强制编译器将未知数量的内存分配到堆栈上?如果它们是按值发送还是按引用发送,与堆栈内存方面有区别吗?

推荐答案

C标准是否允许编译器重用技术上仍在作用域内的内存?

内存不在范围内.作用域用于标识符.对象有生命周期.1

在函数内声明的复合文字具有与其封闭块相关联的自动生存期.因此,即使编译器不能看到函数foo的定义,它也知道复合文字存在(在C标准的抽象模型中),直到main结束.它可以分析main,并看到对象没有在其他地方使用,因此它被允许优化它们的空间-一旦程序使用完它们,它就可以重新使用它们的空间-就像它可以对命名对象(变量)一样.

但是,如果在foo之后调用了其他函数,而编译器没有这些函数的定义,它就不能知道这些函数没有使用复合文字的对象,因为正如您所注意到的,foo可能已经存储了它们的地址.因此,编译器必须至少保留这些对象,直到最后一次调用它没有定义的函数或它们的抽象生存期结束,以较早的为准.

for循环内的复合文字的分析是相同的,使用for循环的内部块而不是定义main的块.

脚注

1作用域为where在程序source code中可见标识符.在程序execution对象存在期间,生命周期 是when.作用域和生存期之间存在某种关联,因为某些生存期是由程序控制(执行点)和源代码之间的关系决定的,但它们是不同的东西.例如,即使程序控制在其作用域之外,对象也可能存在-您的foo访问通过引用传递的复合文字,即使它们的定义不在其作用域内.

C++相关问答推荐

如何用C(使用两个s补数算术的32位程序)计算

想了解 struct 指针和空指针转换

DPDK-DumpCap不捕获端口上的传入数据包

如何使fputs功能提示错误输入并要求用户重新输入.程序停止而不是请求新的输入

`#if`条件中是否允许`sizeof`?

轮询libusb_pollfd struct 列表的正确方式是什么?

在WSL关闭/重新启动后,是什么原因导致共享对象依赖关系发生更改?

这个C程序在工作中途停止获取输入.我收到分段故障(核心转储).我还是不知道问题出在哪里

如何编写一个for循环来计算C中各项的总和?

不同出处的指针可以相等吗?

如何用C语言为CLI应用程序编写按键检测系统?

使用nmake for程序比Hello World稍微复杂一些

-Wnonnull-Compare警告不是具有误导性吗?

合并对 struct 数组进行排序

强制GCC始终加载常量(即只读),即使启用了优化

可以';t从A9G模块拨打电话

const struct 成员的 typedef 中的灵活数组大小

全局变量 y0 与 mathlib 冲突,无法编译最小的 C 代码

C11 嵌套泛型

为什么 C 字符串并不总是等同于字符数组?