因此,我一直在用 C 语言做一些学习项目,并且开始对指针更加熟悉,但是我遇到了一些似乎找不到太多信息的现象。
基本上,我创建了一个数组,为了简单起见,我从一个整数数组开始:
int x[2] = {1, 3};
现在,如果我想修改这个数组的内容,我发现我可以创建一个函数,该函数以整数指针作为参数,并x
作为参数传递,并x
通过指定索引来取消引用。
#include <stdio.h>
void foo(int* input){
input[0] = 2;
input[1] = 4;
}
int main(){
int x[2] = {1, 3};
printf("x[0] before: %d\nx[1] before: %d\n", x[0], x[1]);
foo(x);
printf("x[0] after: %d\nx[1] after: %d\n", x[0], x[1]);
}
输出
x[0] before: 1
x[1] before: 3
x[0] after: 2
x[1] after: 4
这很有趣,但我并不经常看到这种做法,所以我不确定它是否可以接受。
现在,对于更大的问题,出于某种原因,每当我想通过指定新值和指针,然后将参数设置为该新指针来更改指针本身指向的地址时,它似乎只有在我执行以下操作时才有效:
#include <stdio.h>
#include <stdlib.h>
void foo(int** input){
int x[3] = {2, 4};
int** ptr = x;
*input = *ptr;
}
int main(){
int x[2];
x[0] = 1;
x[1] = 3;
printf("x[0] before: %d\n", x[0]);
printf("x[1] before: %d\n", x[1]);
foo(x);
printf("x[0] after: %d\n", x[0]);
printf("x[1] after: %d\n", x[1]);
return 0;
}
输出:
x[0] before: 1
x[1] before: 3
x[0] after: 2
x[1] after: 4
因此,我有几个问题似乎找不到答案,可能是因为我缺乏技术词汇:
1:每当我传递带有或不带有引用的数组作为参数时,为什么参数表现为指针数组,而没有初始化指针数组并将其设置为数组的地址?这样做会有什么意想不到的后果吗?
2:如果参数是一个指针,而不是指向指针的指针,那么将指针作为参数传递给像我一样接受双指针作为参数的函数是否可以接受,这样做是否存在我应该知道的未定义行为?
3:为什么这会起作用?
将数组作为指针参数的实参是很常见且正常的。数组会自动转换为指向其首元素的指针。
每当数组在表达式中用作 的操作数
sizeof
、一元 的操作数&
、typeof 运算符的操作数或用于初始化数组的字符串文字以外的其他用途时,都会执行此转换。关于此代码:
这太可怕了,你永远不应该这样做。
这里实际发生的情况是:
x
作为参数传入。它被自动转换为指向其首元素的指针。也就是一个int *
值为 的指针&x[0]
。foo
被声明为接受int **
,因此编译器将指针转换为 类型int **
。但它仍然指向x[0]
。int** ptr = x;
初始化ptr
为指向x
局部变量的第一个元素foo
。同样,编译器将指针转换为 类型int **
。*input = *ptr;
,ptr
被解除引用。由于它的类型是int **
,编译器认为它引用的是int *
。在你的 C 实现中,int *
占用 8 个字节,因此编译器会从内存中加载 8 个字节。int
是四个字节,因此八个字节是 twoint
。当编译器从内存中加载八个字节时,它会获取int
指针指向的两个字节,因此它会获取x[0]
和的字节x[1]
(在本地数组中x
)。*input
,编译器期望*input
引用八个字节,并将加载的八个字节写入内存。这是 和x[0]
中x[1]
的内存,因此这些元素会被中main
的字节覆盖。x
foo
这一切“成功”纯属巧合。事实上,这并非 C 标准所定义的行为:这些隐式指针转换未定义,使用错误类型访问内存也未定义。如果你在编译器中启用了优化,程序可能会停止“运行”。
你的第一个程序没有任何问题。但是,你的第二个程序由于违反了严格别名规则而调用了未定义的行为,因为它将
int
当作来访问int *
。您的第二个程序中发生的情况如下:
这条线
在标准 C 语言中是不允许的,因为它会将 a 转换
int *
为 an,int **
且没有进行显式类型转换。编译器 Clang 只会给出警告,而编译器 GCC 则会直接拒绝此行。因此,我假设您使用的是编译器 Clang。编译器 Clang 将把这一行视为使用了显式类型转换:
下一行
您将指针
ptr
视为指向一个 类型的对象int *
,尽管它指向的是一个简单的int
。如前所述,这违反了严格别名规则(从而引发了未定义的行为),但在您的情况下,看起来您很幸运,它按预期工作。这可能是因为您所处的平台上的指针大小为 64 位,恰好与整个数组的大小相同。对于 CPU 来说,复制一个 64 位指针与复制一个包含两个 32 位值的数组是一样的,因为两种情况下数据的总大小都是 64 位。然而,从编译器的角度来看,这并不是一回事,因为你违反了 C 语言的规则,而如果编译器为了优化而重新排列你的代码,这种做法很容易出错。
如果希望函数更改函数中的
foo
变量,使其指向不同的数组,则应将变量设置为指针而不是数组,以便它能够指向不同的数组。然后,如果将一个指针传递给函数,函数可以使用该指针更改指针的值,使其指向不同的数组。以下是示例:x
main
x
x
foo
foo
x
该程序有以下输出: