
#include
int main()
{
int a;
char c;
scanf("%3d%3c",&a,&c);
printf("%d,%c",a,c);
}
前几天在群里看见别人讨论这个代码,觉得挺有意思的,顺便复习了一把计算机组成原理。
这个代码的输入是12345,xyz,输出是11317,4
首先scanf()属于_cdecl调用,其压栈方式为从右到左。而系统栈(调用栈)从低地址开始增长,这意味着在内存中的排布如下:
|--char--|--------int--------|
|---1B---|---------4B--------|
|---00---|----00 00 00 00----|
压栈方式决定的是内存中的位置,但是读取顺序还是由格式控制字符串决定的。
这里面的格式控制参数%3d和%3c,意思是读取的最大宽度为3。这样对于输入12345,xyz而言,先读取123,再读取45,。
假设一开始的内存是00 00 00 00 00,不难看出第一个字节即第一个00是char的,后面四个是int的。
先读入123,并且%3d指明了是读取3个int作为数据存入,这里是存入a
123的十六进制是00 00 00 7B,但是由于是小端存储,所以内存是00 7B 00 00 00。
然后读入字符串45,,这里会发生越界写入,写入到后面int变量到内存位置上。其中在ASCII码中,字符4的十进制是52,而16进制是34,类似地,另外两个的十六进制分别是35和2C。
此时内存是这样的:
34 35 2C 00 00
由于单个char就占一个字节,所以不存在小端存储导致逆序的问题,它直接顺次从变量c表示的位置开始,一个字节存一个char,直到存完3个。
然后进行十六进制转换到十进制得到11317,此处注意要把顺序颠倒回来。

如图,即得最终答案。
小端存储的原则就是低字节存低地址。
地址是内存的编号,低地址就是序号最小的。
而低字节,是最右侧(类似于个位是低位,而十位相对来说是高位,而百位相对是为来说也是高位)
所以十进制数11317的十六进制00 00 2C 35,其低位是存放35的那个位。
而存放在内存中,由于表示的时候习惯于把编号比较低的内存单元放在左侧,加上低字节存储低地址的原则,在内存中就是35 2C 00 00
摘自网络,你们都乱抄,我也不知道出处在哪
stdcall调用方式又被称为Pascal调用方式。在Microsoft C++系列的C/C++编译器中,使用PASCAL宏,WINAPI宏和CALLBACK宏来指定函数的调用方式为stdcall。
stdcall调用方式的函数声明为:
int _stdcall function(int a, int b);
stdcall的调用方式意味着:
上面那个函数翻译成汇编语言将变成:
push b // 先压入第二个参数
push a // 再压入第一个参数
call function //调用函数
在编译时,此函数的名字被翻译为_function@8
cdecl调用方式又称为C调用方式,是C语言缺省的调用方式,它的语法为:
int function(int a, int b) // 不加修饰符就是C调用方式
int _cdecl function(int a, int b) // 明确指定用C调用方式
cdecl的调用方式决定了:
由于是由调用者来恢复堆栈,因此C调用方式允许函数的参数个数是不固定的,这是C语言的一大特色。
此方式的函数被翻译为:
push b // 先压入第二个参数
push a // 在压入第一个参数
call funtion // 调用函数
add esp, 8 // 清理堆栈
在编译时,此方式的函数被翻译成:_function
fastcall按照名字上理解就可以知道,它是一种快速调用方式。此方式的函数的第一个和第二个DWORD参数通过ecx和edx传递
后面的参数从右向左的顺序压入栈。
函数名修饰规则同stdcall
其声明语法为:
int fastcall function(int a, int b);
thiscall 调用方式是唯一一种不能显示指定的修饰符。它是c++类成员函数缺省的调用方式。由于成员函数调用还有一个this指针,因此必须用这种特殊的调用方式。
thiscall调用方式意味着:
参数从右向左压入栈。
如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压入栈后被压入栈。
参数个数不定的,由调用者清理堆栈,否则由函数自己清理堆栈。
可以看到,对于参数个数固定的情况,它类似于stdcall,不定时则类似于cdecl。
是一种比较少见的调用方式,一般高级程序设计语言中不常见。
函数的声明调用方式和实际调用方式必须一致,必然编译器会产生混乱。
在考虑实在不行要不抽时间看看《程序员的自我修养——链接、装载与库》
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个@符号和其参数的字节数,格式为_function@8。
__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_function。
__fastcall调用约定在输出函数名前加上一个@符号,后面也是一个@符号和其参数的字节数,格式为@function@8。
它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。
__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”代表一次重复
(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++调用。