• [C语言]水群杂记


    正文

    在这里插入图片描述

    #include
    int main()
    {
    	int a;
    	char c;
    	scanf("%3d%3c",&a,&c);
    	printf("%d,%c",a,c);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    前几天在群里看见别人讨论这个代码,觉得挺有意思的,顺便复习了一把计算机组成原理。

    这个代码的输入是12345,xyz,输出是11317,4

    首先scanf()属于_cdecl调用,其压栈方式为从右到左。而系统栈(调用栈)从低地址开始增长,这意味着在内存中的排布如下:

    |--char--|--------int--------|
    |---1B---|---------4B--------|
    |---00---|----00 00 00 00----|
    
    • 1
    • 2
    • 3

    压栈方式决定的是内存中的位置,但是读取顺序还是由格式控制字符串决定的。

    这里面的格式控制参数%3d%3c,意思是读取的最大宽度为3。这样对于输入12345,xyz而言,先读取123,再读取45,

    假设一开始的内存是00 00 00 00 00,不难看出第一个字节即第一个00char的,后面四个是int的。

    先读入123,并且%3d指明了是读取3个int作为数据存入,这里是存入a

    123的十六进制是00 00 00 7B,但是由于是小端存储,所以内存是00 7B 00 00 00

    然后读入字符串45,,这里会发生越界写入,写入到后面int变量到内存位置上。其中在ASCII码中,字符4的十进制是52,而16进制是34,类似地,另外两个的十六进制分别是352C

    此时内存是这样的:

    34 35 2C 00 00

    由于单个char就占一个字节,所以不存在小端存储导致逆序的问题,它直接顺次从变量c表示的位置开始,一个字节存一个char,直到存完3个。

    然后进行十六进制转换到十进制得到11317,此处注意要把顺序颠倒回来。

    在这里插入图片描述

    如图,即得最终答案。

    存储方式

    小端存储的原则就是低字节存低地址

    地址是内存的编号,低地址就是序号最小的。

    而低字节,是最右侧(类似于个位是低位,而十位相对来说是高位,而百位相对是为来说也是高位)

    所以十进制数11317的十六进制00 00 2C 35,其低位是存放35的那个位。

    而存放在内存中,由于表示的时候习惯于把编号比较低的内存单元放在左侧,加上低字节存储低地址的原则,在内存中就是35 2C 00 00

    函数的调用方式

    摘自网络,你们都乱抄,我也不知道出处在哪

    stdcall

    stdcall调用方式又被称为Pascal调用方式。在Microsoft C++系列的C/C++编译器中,使用PASCAL宏,WINAPI宏和CALLBACK宏来指定函数的调用方式为stdcall。

    stdcall调用方式的函数声明为:

    int _stdcall function(int a, int b);
    
    • 1

    stdcall的调用方式意味着:

    1. 参数从右向左一次压入堆栈
    2. 由被调用函数自己来恢复堆栈
    3. 函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的尺寸

    上面那个函数翻译成汇编语言将变成:

    push b     // 先压入第二个参数
    push a     // 再压入第一个参数
    call function   //调用函数
    
    • 1
    • 2
    • 3

    在编译时,此函数的名字被翻译为_function@8

    cdecl

    cdecl调用方式又称为C调用方式,是C语言缺省的调用方式,它的语法为:

    int function(int a, int b)  // 不加修饰符就是C调用方式
    int _cdecl function(int a, int b)  // 明确指定用C调用方式
    
    • 1
    • 2

    cdecl的调用方式决定了:

    1. 参数从右向左依次压入堆栈
    2. 由调用者恢复堆栈
    3. 函数名自动加前导下划线

    由于是由调用者来恢复堆栈,因此C调用方式允许函数的参数个数是不固定的,这是C语言的一大特色。

    此方式的函数被翻译为:

    push b   // 先压入第二个参数
    push a   // 在压入第一个参数
    call funtion  // 调用函数
    add  esp, 8   // 清理堆栈
    
    • 1
    • 2
    • 3
    • 4

    在编译时,此方式的函数被翻译成:_function

    fastcall

    fastcall按照名字上理解就可以知道,它是一种快速调用方式。此方式的函数的第一个和第二个DWORD参数通过ecx和edx传递

    后面的参数从右向左的顺序压入栈。

    函数名修饰规则同stdcall

    其声明语法为:

    int fastcall function(int a, int b);
    
    • 1

    thiscall

    thiscall 调用方式是唯一一种不能显示指定的修饰符。它是c++类成员函数缺省的调用方式。由于成员函数调用还有一个this指针,因此必须用这种特殊的调用方式。

    thiscall调用方式意味着:

    1. 参数从右向左压入栈。

    2. 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压入栈后被压入栈。

    3. 参数个数不定的,由调用者清理堆栈,否则由函数自己清理堆栈。

    可以看到,对于参数个数固定的情况,它类似于stdcall,不定时则类似于cdecl。

    naked call

    是一种比较少见的调用方式,一般高级程序设计语言中不常见。

    函数的声明调用方式和实际调用方式必须一致,必然编译器会产生混乱。


    在考虑实在不行要不抽时间看看《程序员的自我修养——链接、装载与库》

    函数名字修改规则

    1. C编译时函数名修饰约定规则:

    __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个@符号和其参数的字节数,格式为_function@8

    __cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_function

    __fastcall调用约定在输出函数名前加上一个@符号,后面也是一个@符号和其参数的字节数,格式为@function@8。

    它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。

    2. C++编译时函数名修饰约定规则:

    __stdcall调用约定:

    (1)以“?”标识函数名的开始,后跟函数名;

    (2)函数名后面以“@@YG”标识参数表的开始,后跟参数表;

    (3)参数表以代号表示:

    X--void
    D--char
    E--unsigned char
    F--short
    H--int
    I--unsigned int
    J--long
    K--unsigned long
    M--float
    N--double
    _N--bool
    PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (4)参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;

    (5)参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

    其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如:

    int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”

    void Test2() -----“?Test2@@YGXXZ”

    __cdecl调用约定:

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。

    __fastcall调用约定:

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。

    VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用。

  • 相关阅读:
    驱动开发:内核扫描SSDT挂钩状态
    【网页前端】CSS样式表入门概述以及基本语法格式和选择器
    勤奋型人格分析,勤奋型人格如何做职业规划
    Android应用性能优化
    MYSQL 同步到ES 如何设计架构保持一致性
    SELinux零知识学习十八、SELinux策略语言之类型强制(3)
    用c++写平均分
    InnoDB中的索引
    Linux命令大全
    网络基础知识
  • 原文地址:https://blog.csdn.net/qq_39377889/article/details/127568282