前文链接:上次由于一个很常见的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:\n");
printf("\tEndian: %s\n", Endian());
printf("\tStackFrameDirection: %s\n", StackFrameDirection());
printf("\tArgumentsPushOrder: %s\n", ArgumentsPushOrder(a_arg(), a_arg()));
/* Evaluation Order below is determined by Complier and maybe not always same */
printf("\tArgumentsEvaluationOrder: %s\n", 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\n", i); // "%" PRId32
$ ./a.out
0
$

因为树莓派上的 GCC 的数据模型为 ILP32,

所以 printf("%ld\n", 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环境的栈帧增长方向以及传递参数时的压栈顺序的更多相关文章

  1. 大端小端系统_union_栈的增长方向

    一道题引发的思考: 1.看一下之前写的union的特点,理解一下共享内存的概念 2.栈的增长方向是从高地址向低地址增长(数组比较特别,a[0]在低地址,a[n-1]在高地址)(堆由低地址到高地址存储) ...

  2. Windows x64 栈帧结构

    0x01 前言 Windows 64位下函数调用约定变为了快速调用约定,前4个参数采用rcx.rdx.r8.r9传递,多余的参数从右向左依次使用堆栈传递.本次文章是对于Windows 64位下函数调用 ...

  3. c函数调用过程原理及函数栈帧分析

    转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707       今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比 ...

  4. JAVA栈帧

    简介 Java栈是一块线程私有的内存空间.java堆和程序数据相关,java栈就是和线程执行密切相关的,线程的执行的基本行为是函数调用,每次函数调用的数据都是通过java栈来传递的. Java栈与数据 ...

  5. 汇编3栈帧,参数传递,串操作,混合汇编,x64,asm文件

    基础知识2 选择结构 通过判断 + 条件跳转指令来实现 循环结构 通过判断 + 条件跳转指令来实现(会有一个向上跳转的语句) 函数调用约定 C调用约定: 由外部平衡栈 标准调用约定 : 由函数内部平衡 ...

  6. 图解JVM字节码执行引擎之栈帧结构

    一.执行引擎      “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...

  7. 栈帧%ebp,%esp详解

    首先应该明白,栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部( ...

  8. Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)

    Java虚拟机运行时栈帧结构(周志明书上P237页) 栈帧是什么? 栈帧是一种数据结构,用于虚拟机进行方法的调用和执行. 栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元. 2018.1.2更新(在 ...

  9. 栈帧 2.6. Frames 虚拟机内存模型

    https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.6 小结: 1. https://docs.oracle. ...

随机推荐

  1. [Java] String类, StringBuffer类

    1. String 类 1. 创建: String s1 = new String; s1 = "abc"; String s2 = new String("abc&qu ...

  2. BZOJ_1812_[Ioi2005]riv_树形DP

    BZOJ_1812_[Ioi2005]riv_树形DP Description 几乎整个Byteland王国都被森林和河流所覆盖.小点的河汇聚到一起,形成了稍大点的河.就这样,所有的河水都汇聚并流进了 ...

  3. ORACLE PL/SQL 实例精解之第三章 PL/SQL中的SQL

    3.1 在PL/SQL中使用DML 在PL/SQL语块中,两种变量赋值a. := 初始化.b. select into语法,PL/SQL语块的声明部分被声明的变量,后期可以使用选择语句进行赋值. 3. ...

  4. (4)ASP.NET Core 中间件

    1.前言 整个HTTP Request请求跟HTTP Response返回结果之间的处理流程是一个请求管道(request pipeline).而中间件(middleware)则是一种装配到请求管道以 ...

  5. Codeforces - 9D - How many trees? - 简单dp - 组合数学

    https://codeforces.com/problemset/problem/9/D 一开始居然还想直接找公式的,想了想还是放弃了.原来这种结构是要动态规划. 状态是知道怎么设了,$t_{nh} ...

  6. hdoj1078【DP·记忆化搜索】

    还是满水的一道题目吧...这个一看肯定要搜索了..然后又是这么DP,那就是记忆化搜索了...走K步,下一步要比他多...很好写啊/// #include<iostream> #includ ...

  7. python __builtins__ bytearray类 (7)

    7.'bytearray', 返回一个新字节数组.这个数组里的元素是可变的,并且每个元素的值范围: 0 <= x < 256. class bytearray(object) | byte ...

  8. 为什么使用Stylus

    CSS预处理器的出现大大的提高了前端开发的效率和逼格,它让CSS可编程化.LESS和Sass/SCSS是两种最为常见的预处理器,拥有大量的用户基数,数目庞杂的第三方库.然而,还有一种预处理器并未引起足 ...

  9. Python递归和迭代

    递归 在函数内部,调用函数自身的编程技巧称为递归( recursion).递归函数结构清晰,很直观的理解计算过程,但也有严重缺点:相对于普通循环而言,递归运行效率较低,经过很多冗余的计算,递归会消耗大 ...

  10. LCA UESTC 92 Journey

    题目传送门 题意:先给一棵树,然后有一条额外的边,问u走到v从现在最短的路走和原来不加边走的路节省了多少距离 分析:首先跑不加边的树的LCA,这样能求出任意两点的距离,那么现在x和y多连了一条边,如果 ...