C 编译器是否可以在某些情况下不遵守调用约定?

一个函数在同一个文件里被多处反复调用,每次调用都需求寄存器出入栈,可是内联又会导致代码胀大。 着重:同一个文件,同一个文件,同一个文件,没有外部链接,没有外部链接,没有外部链接 这种情况下,可不可以生成一个不遵守调用约定的函数,减少寄存器出入栈,统筹功能和代码巨细? 举个例子:多返回值都存在寄存器里

一个函数在同一个文件里被多处重复调用,每次调用都需求寄存器收支栈,可是内联又会导致代码膨胀。 强调:同一个文件,同一个文件,同一个文件,没有外部链接,…

给TA打赏
共{{data.count}}人
人已打赏
时下

练琴努力真的抵得过天赋吗?

2022-9-22 15:42:16

时下

怀孕早期,孕妇有哪些注意事项?

2022-9-22 15:42:18

5 条回复 A文章作者 M管理员
  1. DBinary

    可以,很多MCU的编译结果函数调用优化后可能都用不到栈,直接使用寄存器传参

    也就是所谓的_fastcall,但fastcall显然是有适用条件的,例如有的时候你需要对参数取地址,并不是所有的MCU的寄存器都有地址映射

    返回值存在寄存器里在大多数C编译器都适用,但如果是返回结构体的话,一般情况下并不会存储在寄存器里,这涉及到结构体成员寻址问题,你把所有成员变量都分布在不同寄存器里以后如何寻址,但如果非要做,也不是不能做,靠后期编译优化,部分适用函数也是能做到这种优化的,无非是做编译优化的多加加班.

    但为什么MCU/IC出厂配套对应C编译链已经是业内共识,为什么不选C++/rust/go...之类的?究其根本:

    1.最重要的C 编译工具链实现起来较为简单,如果你看不明白一件事,你就往这玩意能不能赚钱或者省钱上看.

    2.拓展性强,你可以很容易捏一个编译特性(编译器拓展)以适配自己的IC(这是一把双刃剑,特殊的编译拓展会牺牲代码移植性,但完全统一的编译标准,注定只能适配部分的底层环境)

    3.C作为老牌语言,已经在长远的时间内证明了其在工业界的稳定性与成熟性,学习相对简单,容易被大多数人接受.

    最后一个拓展问题:C编译器是否可以在某些情况下不遵守调用约定(甚至是不遵守C语言标准)?

    答:有没有想过,在底层环境中所谓的C语言,也许只是一个长得很像C语言的语言.

    小贴士:Compiler engineer都是很傲娇的,如果你给ta一套编译标准,ta会表示"你说标准就标准,那我不是很没面子!".

  2. 知乎用户

    从直觉上来说,不让内联的话,可以假设是比较长的函数,那这个函数本身可能就有许多次寄存器出入栈。相比之下,不遵守调用约定得到的好处就很小了。

    从机制上来说,还是小函数去内联靠谱一些,不想遵守 calling convention 一般还是比较麻烦的事情,从硬件、编译器到操作系统,无一不遵守它。

    楼下应该对 calling convention 有一些误解,当它成为规则的时候,就已经是 calling convention了,对叶子函数的尾调用优化,和遵守 calling convention 并不矛盾。

    题主想不遵守 calling convention 就把函数调用了,理论上是没有问题的。就是不能使用现成的函数调用指令,也没有现成的编译器可供使用而已。

    编译器一般不会做不遵守 calling convention 的事情,因为它在处理一个函数的时候,看不到另外一个函数,正常设计都假设遵守

  3. alvmu

    当然可以,只要确保函数不被外部单元调用,你需要尽量使用static去修饰函数,编译器可以修改它的调用约定,比如msvc x86默认是cdecl,有可能优化成寄存器传参。gcc我记得也有类似的优化。

    随便举例一个违反函数调用约定的例子。

    #include <cstdio>
    #include <cstdlib>
    
    __declspec(noinline) int __cdecl add(int l, int r) noexcept
    {
    	return l + r;
    }
    
    int main(int argc, char* argv[])
    {
    	if (argc == 3)
    	{
    		int l = std::atoi(argv[1]);
    		int r = std::atoi(argv[2]);
    		return add(l, r);
    	}
    	return 0;
    }
    

    直接被优化成fastcall,msvc x86编译后的结果如下:

    add:

    lea eax, [ecx+edx];

    retn;

  4. 望山

    可以,但是我怀疑会不会有哪个编译器这么做,因为可以适用的情形太罕见,收益实在太小了。而付出的代价,我首先想到的是调试器将看到错误的局部变量数值。

  5. 知乎用户

    理论上,一个tu内部声明为static的函数,确实编译器有完全自主选择调用方式的空间。

    但是,问题在于,取消传参,实际性能到底能提高多少?又或者说,全寄存器传参的适用场景到底有多少?

    哪怕我举通用寄存器比较多的x86-64,能用的也不过16个。满打满算,你最多也就传16个参数(假定你无返回值)。更何况,你的函数在内部运行时,不再需要额外的寄存器?如果是真的非常简单的函数,当然也有可能——但这种这么简单的函数,真把它inline了,代码又能膨胀多少?

    反过来,一个较长较复杂的函数,其内部需要用到的寄存器肯定不会少,这样,就一定挤占你能用于传参的寄存器(要么就是寄存器传进来后再找机会挪到栈上——这又不是同样绕回来了?)。所以,绕来绕去,就和现在的调用约定也就没什么差别了。

    总之,这种方案,实际上就是在现有调用约定和inline之间想找到一个立足的场景和空间,但这个空间到底有多大,是否足以让编译器厂商特意为此设计一套单独的机制,这是个很大的问号。

    至于说多返回值?

    本质上C/C++都不支持多返回值(都是用单个struct/class来模拟的)。如果你想改这个,本质上你就是创造新的语法,当然,这也不是不行,但这就让你的编译器的适用场景产生很大的限制了。

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索