C/C++中的函数参数传递机制

一、 函数参数传递机制的基本理论
  函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的 参数传递机制有两种:值传递和引用传递。以下讨论称调用其他函数的函数为主调函数,被调用的函数为被调函数。
  值传递(passl-by- value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个 副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
  引用传递(pass-by- reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函 数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的
实 参变量。

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

  在C 语言中,值传递是唯一可用的参数传递机制。但是据笔者所知,由于受指针变量作为函数参数的影响,有许多朋友还认为这种情况是引用传递。这是错误的。请看下 面的代码:
int swap(int *x, int *y)
{
int temp;
temp = *x; *x = *y; *y = temp;
return temp;
}
void main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2 = &b;
swap(p1, p2)
}
   函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了 swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的汇编代码可以看出(注释是笔者加的):
22: void main()
23: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1
0040108F mov dword ptr [ebp-8],2
14: int *p1 = &a;
00401096 lea eax,[ebp-4]
00401099 mov dword ptr [ebp-0Ch],eax
15: int *p2 = &b;
0040109C lea ecx,[ebp-8]
0040109F mov dword ptr [ebp-10h],ecx
16: swap(p1, p2);
004010A2 mov edx,dword ptr [ebp-10h] ;参数p2的值进栈
004010A5 push edx
004010A6 mov eax,dword ptr [ebp-0Ch] ;参数p1的值进栈
004010A9 push eax
004010AA call @ILT+15(swap) (00401014) ;调用swap函数
004010AF add esp,8 ;清理堆栈中的参数
17: }
  阅读上述代 码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用 swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap 将使用这些进入堆栈的变量值。下面是swap函
数的汇编代码:
14: void swap(int *x, int *y)
15: {
00401030 push ebp
00401031 mov ebp,esp ;ebp指向栈顶
……
……
16: int temp;
17: temp = *x;
4: int temp;
5: temp = *x;
00401048 mov eax,dword ptr [ebp+8] ;操作已存放在堆栈中的p1,将p1置
; 入eax
0040104B mov ecx,dword ptr [eax] ;通过寄存器间址将*p1置入ecx
0040104D mov dword ptr [ebp-4],ecx;经由ecx将*p1置入temp变量的内
;存单元。以下类似
6: *x = *y;
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
7: *y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return temp;
00401062 mov eax,dword ptr [ebp-4]
9: }
  由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指 针的值而已。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:
  1. 进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。
  2. C语言中参数是从右向左进栈的。
  3. 被调函数使用的堆栈区域结构为:
    局部变量(如temp)
    返回地址
    函数参数
    低地址
     高地址
  4. 由主调函数在调用后清理堆栈。
  5. 函数的返回值一般是放在寄存器中的。
  这里尚需补充说明几点: 一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如structure),采用从 源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆 盖。三是函数的返回值如果很大,则从堆栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。对于 第二和第三点,《Thinking in C++》一书在第10章有比较好的阐述。四是一个显而易见的结论,如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于堆栈中,调用结束后 堆栈将被清理,这些地址就变得无效了。

三、 C++语言中的函数参数传递机制
   C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中。 在C++中声明引用传递要使用"&"符号,而调用时则不用。下面的代码是使用引用传递的swap2函数和main函数:
int& swap2(int& x, int& y)
{
int temp;
temp = x;
x = y;
y = temp;
return x;
}

void main()
{
int a = 1, b = 2;
swap2(a, b);
}
   此时函数swap2将接受两个整型变量的地址,同时返回一个其中的一个。而从main函数中对swap2的调用swap2(a, b)则看不出是否使用引用传递,是否使用引用传递,是由swap2函数的定义决定的。以下是main函数的汇编代码:
11: void main()
12: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1 ;变量a
0040108F mov dword ptr [ebp-8],2 ;变量b
14: swap2(a, 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 ;清理堆栈中的参数
15: }
可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地
址 压栈,这就是在传递变量的地址。此时swap2函数的汇编代码是:
2: int& swap2(int& x, int& y)
3: {
00401030 push ebp
00401031 mov ebp,esp
……
……
4: int temp;
5: temp = x;
00401048 mov eax,dword ptr [ebp+8]
0040104B mov ecx,dword ptr [eax]
0040104D mov dword ptr [ebp-4],ecx
6: x = y;
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
7: y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return x;
00401062 mov eax,dword ptr [ebp+8] ;返回x,由于x是外部变量的偏移地
;址, 故返回是合法的
9: }
  可以看出,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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 C/C++ 程序用 CreateThread 函数创建新线程播放音乐与主线程同时运行的方法如下: 1. 首先你需要包含 Windows 头文件 `Windows.h`。 2. 然后你需要声明一个线程函数,并指定线程函数的类型为 `DWORD WINAPI`。线程函数的参数是一个指向 void 类型的指针,你可以在这个指针所指向的内存存储一些需要在线程使用的数据。 3. 然后你可以使用 CreateThread 函数创建一个新线程,并将线程函数的地址作为参数传入。 下面是一个示例代码: ```cpp #include <Windows.h> DWORD WINAPI PlayMusic(LPVOID lpParameter) { // 在这里播放音乐 return 0; } int main() { // 创建新线程 HANDLE hThread = CreateThread(NULL, 0, PlayMusic, NULL, 0, NULL); if (hThread == NULL) { // 创建线程失败 return -1; } // 等待线程结束 WaitForSingleObject(hThread, INFINITE); return 0; } ``` 在这个示例,我们在主线程创建了一个新线程,并让新线程执行 PlayMusic 函数,在这个函数播放音乐。主线程在调用 CreateThread 函数后会继续往下执行,并调用 WaitForSingleObject 函数等待新线程结束。这样主线程和新 ### 回答2: 在C/C++程序,可以使用`CreateThread`函数来创建新线程来播放音乐,同时保持主线程的运行。下面是一个基本示例: 首先,需要包含Windows.h头文件,然后使用`CreateThread`函数来创建新线程。同时,我们需要在新线程实现音乐播放的功能。下面是一个示例代码: ```c #include <Windows.h> #include <stdio.h> #include <stdlib.h> DWORD WINAPI PlayMusic(LPVOID lpParam) { // 在此处实现音乐播放的具体逻辑 printf("新线程播放音乐\n"); Sleep(5000); // 假设音乐播放需要5秒钟 // 结束线程 ExitThread(0); } int main() { HANDLE hThread; DWORD threadId; // 创建新线程 hThread = CreateThread(NULL, 0, PlayMusic, NULL, 0, &threadId); if (hThread == NULL) { printf("创建线程失败\n"); return 1; } // 在此处编写主线程的其他逻辑 printf("主线程继续运行\n"); Sleep(2000); // 假设主线程运行需要2秒钟 // 等待新线程结束 WaitForSingleObject(hThread, INFINITE); return 0; } ``` 在上述代码,`PlayMusic`函数是新线程的入口函数,在该函数内实现了音乐播放的具体逻辑。主线程继续运行,并在某个时刻使用`WaitForSingleObject`函数等待新线程的结束。 需要注意的是,由于C/C++程序是单线程的,必须依赖于Windows提供的多线程机制来实现同时运行多个任务。因此,我们需要使用`CreateThread`函数创建新线程。 此外,为了确保线程安全,需要适当使用同步机制如互斥量(Mutex)等来保护共享资源,以防止多线程访问冲突。在具体的音乐播放逻辑,可能需要考虑这些方面。 ### 回答3: 使用CreateThread函数创建新线程可以实现在C/C++程序同时播放音乐和运行主线程。以下是具体步骤: 1. 首先,将音乐播放功能封装在一个函数,例如"playMusic"。这个函数会负责打开音乐文件、播放音乐以及关闭文件等操作。 2. 在主线程,使用CreateThread函数创建一个新线程,并传入指向playMusic函数的指针作为参数。例如: ```c++ HANDLE hThread; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)playMusic, NULL, 0, NULL); ``` 这里,第一个参数为线程安全属性,默认设置为NULL;第二个参数为栈的大小,设置为0表示使用默认大小;第三个参数为线程函数的指针,将playMusic函数的地址强制转换为LPTHREAD_START_ROUTINE类型;第四个参数为传递给线程函数的参数,没有额外参数则设置为NULL;最后两个参数为线程的创建选项和线程id,设置为NULL。 3. 然后,主线程可以继续执行其他任务,不受音乐播放的影响。 4. 为了保证主线程不会在音乐播放结束之前退出,可以在主线程调用WaitForSingleObject函数等待新线程的结束。 ```c++ WaitForSingleObject(hThread, INFINITE); ``` 这里的hThread为新线程的句柄,INFINITE表示等待无限长时间,直到新线程结束。 通过以上步骤,就可以实现在C/C++程序使用CreateThread函数创建新线程并同时播放音乐和运行主线程。新线程会负责音乐播放,而主线程可以继续执行其他任务,直到音乐播放结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值