C/C++:为什么 iostream 在运行时分配的内存比 printf 多?

C/C++: Why does an iostream allocate more memory at runtime than printf?

提问人:anothergreenhorse 提问时间:10/4/2023 最后编辑:anothergreenhorse 更新时间:10/4/2023 访问量:147

问:

我今天注意到了这一点,觉得很有趣:

我首先用 C 语言编译了一个基本的“hello world”,然后用 C++ 编译了

#include <stdio.h>

int main() {
  printf("a\n");
  return 0;
}
#include <iostream>

int main() {
  std::cout << "a" << std::endl;
  return 0;
}

然后他们俩都跑过了瓦尔格林德。C版本(即使使用g++编译)显示分配了1,024个字节,而C++版本显示分配了73,728个字节

这似乎是一个相当大的差异--那里到底发生了什么?

再想一想,为什么运行 C 版本甚至需要整整一千字节?

感谢任何可以帮助我理解的人!

更新

感谢大家的回复!

看起来 include 的一次性初始化成本略高于 72,000 字节,并且它本身仅使用与 相同的千字节。<iostream>coutprintf

只是为了踢球,我写了以下内容并对其进行了测试:

#include <stdio.h>

class minified_output_stream {
    void output(const char* text) {
        printf(text);
    }

    public:
        minified_output_stream& operator<<(const char* text) {
            output(text);
            return *this;
        }
};

int main() {
    minified_output_stream min_cout;

    min_cout << "A\n";
    return 0;
}

并发现,根据 valgrind 的说法,使用这种“模仿 cout”的二进制文件在运行时只分配了 1 KB 的内存。它可能不完全实用,但它绝对很有趣,并且有助于(或至少帮助我)更多地了解 C++ 与 C 中发生的事情

C++ C 内存 分配

评论

1赞 Sam Varshavchik 10/4/2023
这里发生的事情是 C++ 库在做它的工作。
5赞 Andrew Henle 10/4/2023
祝贺。您已经发现了软件膨胀。;-)
0赞 Eugene Sh. 10/4/2023
您还可以查看分配了多少...malloc(1)
1赞 pmg 10/4/2023
您可能会喜欢 muppetlabs.com/~breadbox/software/tiny/teensy.html 上的文章,其中解释了您提出的一些问题......并且比你预期的更详细地介绍其他东西:)
2赞 Sam Varshavchik 10/4/2023
您可能会惊讶地发现,需要完成多少工作,以及输出流必须执行哪些操作才能将输出传递到流,然后为出色完成工作而拍拍自己的背。它涉及构建哨兵对象、缓冲区、做各种令人惊奇的事情......这是一项艰巨的工作。iostreams 以膨胀而闻名。<<

答:

0赞 Petr Skocik 10/4/2023 #1

我在我的 x86-64 Linux 上得到了相同的结果。在循环中运行它不会显示重复的分配,这意味着它是一次性初始化的事情。 运行零次表明,使用 g++,更大的 alloc(72,704 字节)只是因为使用 gcc 而不是 gcc 进行编译而发生的,因此它是 c++ 标准库初始化的一部分。 剩余的 1024 个一次性分配与 相同。可能与 stdio 相关的一些全局状态初始化。仔细检查后,它是 stdout 输出缓冲区的分配。它是 1024,因为输出文件被检测为终端,默认情况下仅执行行缓冲。重定向到非终端,例如 /dev/null,会导致分配转到 4096 进行完全缓冲。g++printf

评论

1赞 anothergreenhorse 10/4/2023
感谢您运行该检查!每个人都得到了有用的答案。看起来包括 <iostream> 的一次性初始化成本为 72,704 字节,而 cout 本身只分配与 printf 相同的 1 KB。这一切对我来说都非常迷人
1赞 Ben Voigt 10/4/2023
@anothergreenhorse:是的,C++流都使用 facet 对象,其中字符类型表之类的内容可以从流本身访问(通过指针链)。使优化程序难以删除它们。尽管它可以证明您永远不会调用任何使用该信息的与语言环境相关的函数,但不允许更改类的内存布局。
0赞 Petr Skocik 10/4/2023
@anothergreenhorse我太懒了/太忙了,无法真正找出原因,但从表面上看,malloc 是从 glibc 的 filedoalloc.c 文件中调用的,该文件似乎正在为 stdout 分配缓冲区。为什么只有1024?当我这样做时,大小增加到 4096,这对于 IO 缓冲区来说更合理。1024 的原因很可能是 libc 检测到输出的终端(在无重定向的情况下),该终端仅获得行填充。1024 似乎足以用于行缓冲 + 作为 printf(“%d”, 42000) 转换等内容的工作区。valgrind a.out >/dev/null
0赞 Petr Skocik 10/4/2023
@anothergreenhorse iostream 至少与 libcs stdio 同步(理论上它也可以重用相同的输出缓冲区),因此 stdio 需要初始化相同。