函数参数传递方式
先说结论:三种传递方式,1.按值传递 2.通过指针模拟”按引用传递“ 3.数组作为参数的特殊情况
1.按值传递
函数调用时,是将实参的值复制给形参。形参和实参是内存中两个独立的变量,修改形参的值不会影响实参。
void modify(int x) {
x = 10; // 修改形参x的值
}
int main() {
int a = 5;
modify(a);
printf("%d", a); // 输出5,实参a未被修改
return 0;
}
2.通过指针模拟”按引用传递“
虽然c语言没有真正的引用传递,但可以通过传递指针(内存地址的值)来间接修改实参的值。
此时传递的是 指针变量的值(地址的拷贝),但可以通过地址访问同一块内存。
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp; // 通过指针修改实参的值
}
int main() {
int x = 1, y = 2;
swap(&x, &y); // 传递x和y的地址
printf("%d %d", x, y); // 输出2 1,实参被修改
return 0;
}
3.数组作为参数的特殊情况
数组作为函数参数时,会退化为指向数组首元素的指针。传递的是数组首地址的拷贝,所以可以通过指针修改原数组的内容。
void modifyArray(int arr[], int size) {
arr[0] = 100; // 修改数组首元素的值
}
int main() {
int a[] = {1, 2, 3};
modifyArray(a, 3);
printf("%d", a[0]); // 输出100,原数组被修改
return 0;
}
注意事项:
结构体的传递:直接传递结构体会导致完整拷贝(按值传递),会影响性能。通常使用指针传递结构体
void modifyStruct(struct MyStruct *s) {
s->value = 10; // 通过指针修改结构体成员
}
指针变量的值传递:如果需要修改指针本身(如动态内存分配),需传递指针的指针(int **p)
void changePointer(int *p) {
p = NULL; // 修改的是形参p的值,不影响实参的指针
}
明白以上概念后来看几道题
问题一
//分析以下代码的问题
void GetMemory1(char *p)
{
p = (char*)malloc(100);
}
void Test1(void)
{
char *str = NULL;
GetMemory1(str); //把str传进去,str是一个指针
strcpy(str, "hello world");
printf(str);
}
问题:
-
GetMemory1(str); 传入的是 str指针的拷贝,也就是地址的拷贝。在GetMemory1函数中,修改的是 局部指针变量 p 的指向,使其指向新分配的 100字节内存,但这不会影响外部的 str指针。所以,函数返回后,str指针仍指向NULL,strcpy试图向NULL地址写数据,导致段错误。
-
GetMemory1函数中分配的内存永远无法被释放,因为函数返回后,局部变量被销毁,分配的内存地址丢失,造成内存泄漏。
解决方案:
-
使用二级指针,传入指针的地址
void GetMemory1(char **p) { // 参数改为指向指针的指针 *p = (char*)malloc(100); // 解引用修改外部指针 } void Test1() { char *str = NULL; GetMemory1(&str); // 传递str的地址 strcpy(str, "hello world"); printf("%s", str); free(str); // 记得释放内存 }
-
通过返回值传递内存地址
char* GetMemory1() { return (char*)malloc(100); } void Test1() { char *str = GetMemory1(); // 直接接收返回值 strcpy(str, "hello world"); printf("%s", str); free(str); }
问题二
char *GetMemory2(void)
{
char p[] = "hello world";
return p;
}
void Test2(void)
{
char *str = NULL;
str = GetMemory2();
printf(str);
}
问题:
-
GetMemory2函数中定义了一个局部字符数组 p,存储在栈内存中。当该函数执行完毕返回时,其栈帧被销毁,p数组的内存被释放,此时返回的 指针p 指向的是一个已被回收的栈的内存地址(悬空指针)。然后 str指针指向了这个悬空指针,在 printf(str)时,试图访问已释放的内存,导致不可预测的结果(可能输出乱码,崩溃或看似正常运行)
-
char p[] = "hello world";会在栈上创建一个字符数组,并将字符串的内容复制到该数组中。但是该数组是临时创建的(生命周期仅限函数执行期间),与字符串字面量(”hello world“,存储在 .rodata段,地址固定,生命周期为整个程序)的地址无关。
解决方案:
-
直接返回字符串字面量的地址(直接指向常量区)
char* GetMemory2(void) { return "hello world"; // 字符串字面量存储在静态存储区,程序生命周期内有效 } void Test2(void) { char *str = GetMemory2(); printf("%s", str); // 输出正常 }
-
使用 static关键字 延长数组声明周期
char* GetMemory2(void) { static char p[] = "hello world"; // static 使数组存储在静态区,生命周期持续到程序结束 return p; } void Test2(void) { char *str = GetMemory2(); printf("%s", str); // 输出正常 }
-
动态分配堆内存
char* GetMemory2(void) { char* p = (char*)malloc(12); // 分配堆内存 strcpy(p, "hello world"); return p; } void Test2(void) { char *str = GetMemory2(); printf("%s", str); free(str); // 必须手动释放内存! }
问题三
void GetMemory3(char **p, int num)
{
*p = (char*)malloc(num);
}
void Test3(void)
{
char *str = NULL;
GetMemory3(&str, 100);
strcpy(str, "hello");
printf(str);
}
问题:可以打印hello,但仍存在问题
-
未检查malloc是否成功,malloc(num)可能失败(内存不足时返回NULL),若直接对NULL指针进行strcpy操作,会导致段错误。
-
使用 printf(str)存在格式字符串漏洞,若str中包含格式化字符串(%s,%d),printf会将其误认为格式说明符,导致未定义行为(如读取无效内存地址)。
-
内存泄漏,malloc后没有free。
问题四
void Test4(void)
{
char *str = (char *)malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "hello");
printf(str);
}
}
问题:
-
悬空指针与访问已释放内存。free(str)释放了str指向的内存,但不会将指针置为NULL,此时str成悬空指针(指向无效内存)。也就是说指针还是指向那个地址,只是从那个地址开始的100字节的内存被清空了。所以if(str != NULL)永远成立,strcpy(str, "hello") 和 printf(str) 操作了已释放的内存,属于未定义行为,可能导致崩溃。
-
格式字符串漏洞,问题三中说过。
问题五
char *GetMemory5(void)
{
return "hello world";
}
void Test5(void)
{
char *str = NULL;
str = GetMemory5();
printf(str);
}
问题:可以打印hello world,但仍存在问题
-
返回的字符串字面量不可修改,若后续尝试修改内容(str[0] = 'H'),会导致未定义行为。可以使用const修饰str,明确表示不能修改。
-
格式字符串漏洞,问题三中说过。
问题六
swap(int* p1, int* p2)
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
问题:
指针p 没有初始化,是野指针,它指向一个随机地址。*p = *p1会向随机地址写入数据,可能会导致程序崩溃或静默数据被破坏。
总结
int a = 0; //全局初始化区,放入.data段
char *p1; //全局未初始化区,放入.bss段
int main()
{
int b; //栈,栈上空间自动分配,自动回收
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; // 123456\0是字面量存储在.rodata段,p3存储在栈上。
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
}