• 测试当前C环境的栈帧增长方向以及传递参数时的压栈顺序


    前文链接:上次由于一个很常见的printf-bug(下文有提及)引发了我对栈的思考,并写下了一点总结。这次就尝试对不同的C环境进行实践,检测其传递参数的一些性质。

    这是今天写的检查C环境的一段程序、能够判断环境的大小端、栈帧增长方向、传递参数时的压栈顺序、以及参数的求值顺序。
    代码如下:

    #include <stdio.h>
    #include <assert.h>
    #include <inttypes.h>
    
    typedef const char *string_literal;
    
    string_literal Endian() {
        union {
            uint16_t u16;
            uint8_t  u8; /* if FF small endian */
        } u = {.u16 = 0x00FF};
        return u.u8 ? "Small Endian" : "Big Endian";
    }
    
    enum {H2L, L2H} SD;
    string_literal StackFrameDirection()
    {
        static string_literal *addr;
        string_literal rtn;
        return !addr ? addr = &rtn, rtn = StackFrameDirection(), addr = NULL, rtn 
                     : &rtn < addr ? SD = H2L , "High -> Low" : (SD = L2H, "Low -> High");
    }
    
    enum {R2L, L2R} APO;
    string_literal ArgumentsPushOrder(int a, int b)
    {
        (void)StackFrameDirection();
        return (APO = !!SD ^ (&a > &b) ? L2R : R2L) ? "Left -> Right" : "Right -> Left";  
    }
    
    string_literal ArgumentsEvaluationOrder(int a, int b)
    {
        return a < b ? "Left -> Right" : "Right -> Left";
    }
    
    int a_arg() {
        static int cnt;
        return ++cnt;
    }
    
    int main()
    {
        printf("In this C implementation:
    ");
        printf("	Endian:                   %s
    ", Endian());
        printf("	StackFrameDirection:      %s
    ", StackFrameDirection());
        printf("	ArgumentsPushOrder:       %s
    ", ArgumentsPushOrder(a_arg(), a_arg()));
        /* Evaluation Order below is determined by Complier and maybe not always same */
        printf("	ArgumentsEvaluationOrder: %s
    ", ArgumentsEvaluationOrder(a_arg(), a_arg()));
        return 0;
    }
    

    我在macOS(intel)上以及树莓派OS(ARM Cortex-A)上都是这个结果:

    In this C implementation:
    	Endian:                   Small Endian
    	StackFrameDirection:      High -> Low
    	ArgumentsPushOrder:       Left -> Right
    	ArgumentsEvaluationOrder: Left -> Right
    

    在某咸鱼的 win10(intel) mingw 上的结果:

    In this C implementation:
            Endian:                   Small Endian
            StackFrameDirection:      High -> Low
            ArgumentsPushOrder:       Right -> Left
            ArgumentsEvaluationOrder: Right -> Left
    

    !!只有压栈顺序不一样。

    Win下的压栈顺序和 WIN32 缓冲区溢出的知识相互照应了。
    树莓派的压栈顺序又和学 ARM 的 ATPCS 相互照应了。

    所以上次在树莓派(ILP32)上的异常结果的具体原因可以尝试分析一下了:

    int64_t i = 1;
    printf("%ld
    ", i); // "%" PRId32
    
    $ ./a.out
    0
    $
    

    因为树莓派上的 GCC 的数据模型为 ILP32,
    所以 printf("%ld ", i); 可以简化成 F(P32, LL64);
    假设 P32 为 0xFFFFFCD0 , LL64 为 1 即 0x0000000000000001;
    因为参数从左边开始压入栈中,且为小端模式,树莓派的栈是从高地址端向低地址端增长,
    所以传递参数的时候字节的压栈顺序是 FF FF FC D0 00 00 00 00 00 00 00 01;
    按照 C 传递参数以及可变参数 stdarg.h 的原理,printf 会根据 P32 的内容,把更低地址的四个字节00 00 00 00理解成 long 并输出,所以最后输出了0。

    思考:前文检测的规律是有标准的吗?那又是谁制定的呢?

    嗯,ATPCS 是否会在 Linux 上起作用,这点真不好说。
    假如编译器有自己的传参标准的话,那系统调用怎么处理?
    编译器肯定要遵循某种操作系统决定的标准。
    可能编译器为了优化,会选择在程序内部的调用使用自己的标准?

  • 相关阅读:
    c#隐藏和重写基类方法的区别
    c#事件(续委托之后)
    c# 中接口和抽象类的区别
    call 和apply 的一个记录
    string 和 stringbuilder 的区别
    传值和传引用
    值类型和引用类型
    标签选择器与字体连写
    input表单案例
    codeforces 676B B. Pyramid of Glasses(模拟)
  • 原文地址:https://www.cnblogs.com/tjua/p/7805929.html
Copyright © 2020-2023  润新知