首页 资讯 应用技术 高压 设计 行业 低压电器 电路图 关于

嵌入式技术

旗下栏目: 可编程逻辑 嵌入式技术 单片机与DSP 光电技术

C语言中入栈顺序与变量输出

嵌入式技术 | 发布时间:2017-07-08 | 人气: | #评论# |本文关键字:C语言
摘要:1).内存区域划分: 图1 程序运行时的内存区域 如图所示:C程序中,栈区主要存储函数的参数,局部变量等,并且栈底为高地址,栈顶为低地址(如图:由高地址向低地址扩展)。 2).入

1).内存区域划分:

图1 程序运行时的内存区域

如图所示:C程序中,栈区主要存储函数的参数,局部变量等,并且栈底为高地址,栈顶为低地址(如图:由高地址向低地址扩展)。

2).入栈顺序:

A:函数参数的入栈顺序:自右向左

原因:

函数参数的入栈顺序和具体编译器的实现有关。有些参数是从左向右入栈,如:Pascal语言从左到右入栈(不支持变参),被调用者清栈;有些语言还可以通过修饰符进行指定,如:Visual C++;但是C语言(cdecl)采用自右向左的方式入栈,调用者清栈。

这是因为自右向左入栈顺序的好处就是可以动态的变化参数个数。通过堆栈分析可知,自左向右入栈方式中,最前面的参数会被压入栈底。除非知道参数个数,否则无法通过栈指针的相对位移求得最左边的参数。这样就无法实现可变参数。因此,C语言采用自右向左入栈顺序,主要是因为实现可变长参数形式(如:printf函数)。可变长参数主要通过第一个定参数来确定参数列表,所以自右向左入栈后,函数调用时栈顶指针指向的就是参数列表的第一个确定参数,这样就可以了。

例子1:

#include <stdio.h>

void print(int x, int y, int z)

{

printf("x = %d addr %p\n", x, &x);

printf("y = %d addr %p\n", y, &y);

printf("z = %d addr %p\n", z, &z);

}

 

int main()

{

print(1,2,3);//自右向入压栈

return 0;

}

运行结果:

x = 1 addr 0xbfb5c760 //栈顶,后压栈

y = 2 addr 0xbfb5c764

z = 3 addr 0xbfb5c768 //栈底,先入栈

B:局部变量的入栈顺序:

在没有栈溢出保护机制下编译时,所有局部变量按系统为局部变量申请内存中栈空间的顺序,即:先申请哪个变量,哪个先入栈,正向的。也就是说,编译器给变量空间的申请是直接按照变量申请顺序执行的。(见例子2)

在有栈溢出保护机制下编译时,入栈顺序有所改变,先按照类型划分,再按照定义变量的先后顺序划分,即:char型先申请,int类型后申请(与编译器溢出保护时的规定相关);然后栈空间的申请顺序与代码中变量定义顺序相反(后定义的先入栈)。(见例子2)

例子2:stack.c

#include <stdio.h>

 

int main()

{

int a[5] = {1,2,3,4,5};

int b[5] = {6,7,8,9,10};

char buf1[6] = "abcde";

char buf2[6] = "fghij";

int m = -1;

int n = -2;

printf("a[0]  = %3d, addr: %p\n", a[0], &a[0]);

printf("a[4]  = %3d, addr: %p\n", a[4], &a[4]);

printf("b[0]  = %3d, addr: %p\n", b[0], &b[0]);

printf("b[4]  = %3d, addr: %p\n", b[4], &b[4]);

printf("buf1[0] = %3d, addr: %p\n", buf1[0], &buf1[0]);

printf("buf1[5] = %3d, addr: %p\n", buf1[5], &buf1[5]);

printf("buf2[0] = %3d, addr: %p\n", buf2[0], &buf2[0]);

printf("buf2[5] = %3d, addr: %p\n", buf2[5], &buf2[5]);

printf("m  = %3d, addr: %p\n", m, &m);

printf("n  = %3d, addr: %p\n", n, &n);

}

没有栈溢出保护机制下的编译:

$ gcc stack.c -g -o stack -fno-stack-pprotector

$ ./stack

a[0]  =   1, addr: 0xbfa5185c //数组内部,地址由低到高不变

a[4]  =   5, addr: 0xbfa5186c //栈底,高地址

b[0]  =   6, addr: 0xbfa51848

b[4]  =  10, addr: 0xbfa51858

buf1[0] =  97, addr: 0xbfa51842

buf1[5] =   0, addr: 0xbfa51847

buf2[0] = 102, addr: 0xbfa5183c

buf2[5] =   0, addr: 0xbfa51841

=  -1, addr: 0xbfa51838

=  -2, addr: 0xbfa51834  //栈顶,低地址

可以看出入栈顺序:a -> b -> buf1 -> buf2 -> m -> n(先定义,先压栈)

 栈溢出保护机制下的编译:

$ gcc stack.c -g -o stack

$ ./stack

a[0]  =   1, addr: 0xbfc69130 //栈顶

a[4]  =   5, addr: 0xbfc69140

b[0]  =   6, addr: 0xbfc69144

b[4]  =  10, addr: 0xbfc69154 

buf1[0] =  97, addr: 0xbfc69160 //char类型,优先入栈

buf1[5] =   0, addr: 0xbfc69165

buf2[0] = 102, addr: 0xbfc69166

buf2[5] =   0, addr: 0xbfc6916b //栈底

=  -1, addr: 0xbfc69158

=  -2, addr: 0xbfc6915c //int类型,后压栈

 

可以看出入栈顺序:buf2 -> buf1 -> n -> m -> b -> a(char类型先入栈,int类型后入栈;先定义,后压栈)

3).指针越界输出:

例子3:stack1.c

#include <stdio.h>

 

int main()

{

char buf1[6] = "abcef";

char buf2[6] = "fghij";

int a[5] = {1,2,3,4,5};

int b[5] = {6,7,8,9,10};

int m = -1;

int n = -2;

char *p = &buf2[0];

printf("a[0]  = %3d, addr: %p\n", a[0], &a[0]);

printf("a[4]  = %3d, addr: %p\n", a[4], &a[4]);

printf("b[0]  = %3d, addr: %p\n", b[0], &b[0]);

printf("b[4]  = %3d, addr: %p\n", b[4], &b[4]);

printf("buf1[0] = %3d, addr: %p\n", buf1[0], &buf1[0]);

printf("buf1[5] = %3d, addr: %p\n", buf1[5], &buf1[5]);

printf("buf2[0] = %3d, addr: %p\n", buf2[0], &buf2[0]);

printf("buf2[5] = %3d, addr: %p\n", buf2[5], &buf2[5]);

printf("m  = %3d, addr: %p\n", m, &m);

printf("n = %3d, addr: %p\n", n, &n);

printf("p[0]  = %3d, addr: %p\n", p[0], &p[0]);

printf("p[6]  = %3d, addr: %p\n", p[6], &p[6]);

printf("p[-6]  = %3d, addr: %p\n", p[-6], &p[-6]);

printf("p[-42]  = %3d, addr: %p\n", p[-42], &p[-42]);

printf("p[-43]  = %3d, addr: %p\n", p[-43], &p[-43]);

printf("p[-53]  = %3d, addr: %p\n", p[-53], &p[-53]);

printf("p[-54]  = %3d, addr: %p\n", p[-54], &p[-54]);

printf("p[-55]  = %3d, addr: %p\n", p[-55], &p[-55]);

printf("p[-56]  = %3d, addr: %p\n", p[-56], &p[-56]);

printf("p[-57]  = %3d, addr: %p\n", p[-57], &p[-57]);

printf("p[-58]  = %3d, addr: %p\n", p[-58], &p[-58]);

printf("p[-59]  = %3d, addr: %p\n", p[-59], &p[-59]);

}

栈溢出保护机制下的编译:

$ gcc stack1.c -g -o stack1

$ ./stack1

a[0]  =   1, addr: 0xbff5ab6c //栈顶,0xbff5ab6c,低地址

a[4]  =   5, addr: 0xbff5ab7c

b[0]  =   6, addr: 0xbff5ab80

b[4]  =  10, addr: 0xbff5ab90

buf1[0] =  97, addr: 0xbff5aba0 //&p[-6]

buf1[5] =   0, addr: 0xbff5aba5

buf2[0] = 102, addr: 0xbff5aba6 //&p[0]

buf2[5] =   0, addr: 0xbff5abab //栈底,0xbff5abab,高地址--->&p[6]:越界,值随机

=  -1, addr: 0xbff5ab94

=  -2, addr: 0xbff5ab98

p[0]  = 102, addr: 0xbff5aba6 //&buf2[0]

p[6]  =   0, addr: 0xbff5abac //&buf2[6],越界,无初始值,值随机

p[-6]  =  97, addr: 0xbff5aba0 //&buf1[0],越界,已有初始值,buf1[0],p[-6]为97

p[-42]  =   5, addr: 0xbff5ab7c //&a[4]

p[-43]  =   0, addr: 0xbff5ab7b //&a[4] - 1字节,大小0x00 = 0

p[-53]  =   0, addr: 0xbff5ab71 //&a[1] + 1字节,大小0x00 = 0

p[-54]  =   2, addr: 0xbff5ab70 //&a[1]

p[-55]  =   0, addr: 0xbff5ab6f //p[-55]到p[-58]能看出Linux是小端存储。

p[-56]  =   0, addr: 0xbff5ab6e //小端存储:低地址存低位,高地址存高位

p[-57]  =   0, addr: 0xbff5ab6d //a[0]=1,即:0x01 0x00 0x00 0x00(低位到高位)

p[-58]  =   1, addr: 0xbff5ab6c //&a[0]

p[-59]  =   -65, addr: 0xbff5ab6b //&a[0] - 1字节,越界,无初始值,值随机

入栈顺序:(栈底:高地址)buf2 -> buf1 -> n -> m -> b -> a[4] -> a[0](栈顶:低地址)

&p[6]--&p[0]--&p[-6]--------------&p[-42]--&p[-58]--&p[-59]

问题:指针p越界会出现问题,如果在p[-6] = 'k';那么会导致因越界覆盖内存里面buf1[0]的值。


责任编辑:电气自动化网
首页 | 资讯 | 应用技术 | 高压 | 设计 | 行业 | 低压电器 | 电路图 | 关于

Copyright 2017-2018 电气自动化网 版权所有 辽ICP备17010593号-1

电脑版 | 移动版

Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。