您当前的位置:首页 > 互联网教程

c语言指针和函数的问题

发布时间:2025-05-17 16:01:24    发布人:远客网络

c语言指针和函数的问题

一、c语言指针和函数的问题

一、函数参数传递机制的基本理论

函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的参数传递机制有两种:值传递和引用传递。以下讨论称调用其他函数的函数为主调函数,被调用的函数为被调函数。

值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的

二、 C语言中的函数参数传递机制

在C语言中,值传递是唯一可用的参数传递机制。但是据笔者所知,由于受指针变量作为函数参数的影响,有许多朋友还认为这种情况是引用传递。这是错误的。请看下面的代码:

函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的汇编代码可以看出(注释是笔者加的):

00401088 mov dword ptr [ebp-4],1

0040108F mov dword ptr [ebp-8],2

00401099 mov dword ptr [ebp-0Ch],eax

0040109F mov dword ptr [ebp-10h],ecx

004010A2 mov edx,dword ptr [ebp-10h];参数p2的值进栈

004010A6 mov eax,dword ptr [ebp-0Ch];参数p1的值进栈

004010AA call@ILT+15(swap)(00401014);调用swap函数

004010AF add esp,8;清理堆栈中的参数

阅读上述代码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap将使用这些进入堆栈的变量值。下面是swap函

00401031 mov ebp,esp;ebp指向栈顶

00401048 mov eax,dword ptr [ebp+8];操作已存放在堆栈中的p1,将p1置

0040104B mov ecx,dword ptr [eax];通过寄存器间址将*p1置入ecx

0040104D mov dword ptr [ebp-4],ecx;经由ecx将*p1置入temp变量的内

00401050 mov edx,dword ptr [ebp+8]

00401053 mov eax,dword ptr [ebp+0Ch]

00401056 mov ecx,dword ptr [eax]

00401058 mov dword ptr [edx],ecx

0040105A mov edx,dword ptr [ebp+0Ch]

0040105D mov eax,dword ptr [ebp-4]

00401060 mov dword ptr [edx],eax

00401062 mov eax,dword ptr [ebp-4]

由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指针的值而已。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:

1.进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。

2. C语言中参数是从右向左进栈的。

3.被调函数使用的堆栈区域结构为:

4.由主调函数在调用后清理堆栈。

5.函数的返回值一般是放在寄存器中的。

这里尚需补充说明几点:一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如 structure),采用从源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆盖。三是函数的返回值如果很大,则从堆栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。对于第二和第三点,《Thinking in C++》一书在第10章有比较好的阐述。四是一个显而易见的结论,如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于堆栈中,调用结束后堆栈将被清理,这些地址就变得无效了。

三、 C++语言中的函数参数传递机制

C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中。在C++中声明引用传递要使用"&"符号,而调用时则不用。下面的代码是使用引用传递的swap2函数和main函数:

此时函数swap2将接受两个整型变量的地址,同时返回一个其中的一个。而从main函数中对swap2的调用swap2(a, b)则看不出是否使用引用传递,是否使用引用传递,是由swap2函数的定义决定的。以下是main函数的汇编代码:

00401088 mov dword ptr [ebp-4],1;变量a

0040108F mov dword ptr [ebp-8],2;变量b

00401096 lea eax,[ebp-8];将b的偏移地址送入eax

00401099 push eax;b的偏移地址压栈

0040109A lea ecx,[ebp-4];将a的偏移地址送入ecx

0040109D push ecx;将a的偏移地址压栈

0040109E call@ILT+20(swap2)(00401019);调用swap函数

004010A3 add esp,8;清理堆栈中的参数

可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地

址压栈,这就是在传递变量的地址。此时swap2函数的汇编代码是:

00401048 mov eax,dword ptr [ebp+8]

0040104B mov ecx,dword ptr [eax]

0040104D mov dword ptr [ebp-4],ecx

00401050 mov edx,dword ptr [ebp+8]

00401053 mov eax,dword ptr [ebp+0Ch]

00401056 mov ecx,dword ptr [eax]

00401058 mov dword ptr [edx],ecx

0040105A mov edx,dword ptr [ebp+0Ch]

0040105D mov eax,dword ptr [ebp-4]

00401060 mov dword ptr [edx],eax

00401062 mov eax,dword ptr [ebp+8];返回x,由于x是外部变量的偏移地

可以看出,swap2与前面的swap函数的汇编代码是一样的。这是因为前面的swap函数接受指针变量,而指针变量的值正是地址。所以,对于这里的 swap2和前面的swap来讲,堆栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对swap2来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的--这

是引用传递;而对swap来说,这个地址是主调函数通过将实参变量的值压栈而传递进来的--这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。

这里的关键点在于,同样是地址,一个是引用传递中的变量地址,一个是值传递中的指针变量的值。我想若能明确这一点,就不至于将C语言中的以指针变量作为函数参数的值传递情况混淆为引用传递了。

虽然x是一个局部变量,但是由于其值是主调函数中的实参变量的地址,故在swap2中返回这个地址是合法的。

c++中经常使用的是常量引用,如将swap2改为:

Swap2(const int& x; const int& y)

这时将不能在函数中修改引用地址所指向的内容,具体来说,x和y将不能出现在"="的左边。

本文论述了在 C和 c++中函数调用的参数传递机制;同时附带说明了函数返回值的一些问题。本文示例使用的是VC++6.0。

二、写一个C语言函数

1、以下是一个使用栈和队列的函数,用于提取字符串数组中的阿拉伯数字并将其倒序添加到原字符串的剩余部分末尾:

2、def extract_and_reverse_strings(strings):

3、stack.append(char)#将数字添加到栈中

4、queue.append(char)#将非数字字符添加到队列中

5、#将队列中的字符按原顺序添加到结果字符串中

6、#将栈中的数字按倒序添加到结果字符串中

7、strings= ["abc","123","def","456"]

8、result= extract_and_reverse_strings(strings)

9、print(result)#输出:abcdef654321

10、在这个函数中,我们遍历字符串数组并检查每个字符,如果是数字,则将其压入栈中,否则将其添加到队列中。然后,我们先将队列中的字符按原顺序添加到结果字符串中,再将栈中的数字按倒序添加到结果字符串的末尾,最后返回结果字符串。这样就实现了将阿拉伯数字提取出来并倒序添加到原字符串的剩余部分的功能。

三、汇编语言问题

1.一部分代码和格式,是一种古老的用来保证程序结束后能正确返回DOS的算法框架。

START PROC FAR;必须用FAR定义,以保证最后的RET被汇编成远调用返回的RETF

PUSH DS;从这里开始的三条指令,是在堆栈中形成一个指向PSP首址的远指针

详细原理可以参阅相关资料。它涉及到DOS装载EXE程序的PSP结构,以及低版本DOS的INT 20H的功能要求。

高版本的DOS,有了4CH号DOS功能调用后,就不需要使用这种框架格式了。

2. DSEG段名,对应的段地址,是程序加载到内存时才确定的一个常数。在指令中使用的DSEG段名操作数,是立即寻址。MOV指令的寻址组合中,不允许将立即数直接传送到段寄存器。

所以,为了将它装入DS段寄存器,只有借用一个通用寄存器中转。

MOV AX, DSEG;取段地址,此立即数可以送通用寄存器。AX换成BX, SI等也可。

MOV DS, AX;通用寄存器可以传给段寄存器