Liam0205 / liam0205.github.io

Deployment of my weblog.
https://liam0205.github.io
35 stars 5 forks source link

像递归一样的函数模板调用 | 始终 #281

Open Liam0205 opened 5 years ago

Liam0205 commented 5 years ago

https://liam.page/2019/02/19/recursive-like-function-template-calling/

遇到一个蛮奇葩的问题:在 C++ 中如何不用循环和递归,打印从 1 至 N 的自然数? 想了想,用模板元编程可以解决——让编译器在编译期把该打印的东西都展开就好了。

randomwangran commented 5 years ago

太棒了!感谢分享对递归的理解。

但是: 什么是递归呢?

我觉得是函数在定义的时候,包含自己。再外加一个终止条件。

这个是函数定义:

template <int N>
void counting(std::ostream& os) {
  counting<N - 1>(os);  // *
  os << N << std::endl;
}

终止条件:

template <>
void counting<1>(std::ostream& os) {
  os << 1 << std::endl;
}

从远看起来特别像是递归函数。

归实例化的深度是有限制

这个限制是由于物理内存大小的限制么?为什么是 256 层? 用 x64 编译器会好一些么?

Liam0205 commented 5 years ago

@randomwangran

递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。递归一词还较常用于描述以自相似方法重复事物的过程。

C++ 里,由不同的模板实参实例化的函数模板是不同函数。所以在这里不是递归。但你讲的没错,它的形式看起来和函数递归很相似。所以,它在编译的时候,编译器会「递归地实例化」;不过在执行期只是若干不同函数的调用,不会产生递归。

这里递归实例化的深度限制依次来自寻址空间的限制(32bit or 64bit)、可用内存的限制、编译器内某些数据结构的大小限制。你说的两个因素会限制递归实例化的深度,但在这里不是直接原因。直接原因是「编译器就这么设置了一个值」。clang 预设了 256,gcc 预设了 900。就这么直白。当然这个预设值是可以改的;不过这里就不展开讲了。

horqin commented 5 years ago

博主你好,非常感谢你的博文。

根据汇编的方式进行理解可能效果更好?

经过反汇编你的代码,可以发现很多命名为*countint*的函数, 在目标文件层面,可以确认不是递归操作。

Liam0205 commented 5 years ago

@horqin 博主你好,非常感谢你的博文。

根据汇编的方式进行理解可能效果更好?

经过反汇编你的代码,可以发现很多命名为*countint*的函数, 在目标文件层面,可以确认不是递归操作。

从汇编理解确实是个办法。不过这不需要反汇编呀。你有源代码了,直接编译到汇编码停止就好了。例如这个网站所示:

https://gcc.godbolt.org/z/yK0PIV

horqin commented 5 years ago

@Liam0205

@horqin 博主你好,非常感谢你的博文。

根据汇编的方式进行理解可能效果更好?

经过反汇编你的代码,可以发现很多命名为*countint*的函数, 在目标文件层面,可以确认不是递归操作。

从汇编理解确实是个办法。不过这不需要反汇编呀。你有源代码了,直接编译到汇编码停止就好了。例如这个网站所示:

https://gcc.godbolt.org/z/yK0PIV

gcc 与 clang 编译器缺乏 无用标签过滤 操作, 直接生成的汇编代码通常存在很多冗余信息,即存在大量标签。 因此通常需要使用 objdump -d foo.o 命令间接生成可读性更佳的汇编代码。

博主提供的软件 Compiler Explorer,也正在不断改进 无用标签过滤 功能。

因此,反汇编其实正是“编译”与“无用标签过滤”的组合罢了。

Liam0205 commented 5 years ago

@Liam0205

@horqin 博主你好,非常感谢你的博文。 根据汇编的方式进行理解可能效果更好? 经过反汇编你的代码,可以发现很多命名为*countint*的函数, 在目标文件层面,可以确认不是递归操作。

从汇编理解确实是个办法。不过这不需要反汇编呀。你有源代码了,直接编译到汇编码停止就好了。例如这个网站所示: https://gcc.godbolt.org/z/yK0PIV

gcc 与 clang 编译器缺乏 无用标签过滤 操作, 直接生成的汇编代码通常存在很多冗余信息,即存在大量标签。 因此通常需要使用 objdump -d foo.o 命令间接生成可读性更佳的汇编代码。

博主提供的软件 Compiler Explorer,也正在不断改进 无用标签过滤 功能。

因此,反汇编其实正是“编译”与“无用标签过滤”的组合罢了。

你讲的对。