在C语言中,解引用和将变量的地址赋值给指针变量有什么区别?

5

看看下面的两个代码示例!

int main() {
    int a = 12;
    int *p;
    *p = a;
}

而这段代码,
int main() {
    int a = 12;
    int *p;
    p = &a;
}

在第一段代码中,指针被解引用为 *p = a,而在第二段代码中,变量 a 的地址被设置为指针变量。
我的问题是这两段代码之间有什么区别?

3
首先,当您对未定义的指针p进行解引用操作时,会出现UB(未定义行为): - Martin James
3
指针必须始终指向有效的内存。问问自己:“在第一个例子中,p 指向哪里(例如,作为其值的 p 持有哪个有效地址)?” 在第二个例子中,你将 a 的地址赋给了 p —— 现在 p 持有 a 的地址作为其值(例如,p 指向 a)。请记住,指针只是一个正常的变量,它将其他东西的地址作为其值。 - David C. Rankin
3
第一个比喻就像给一个不存在的房子送货包裹,第二个比喻就像写下你朋友家的地址。 - molbdnilo
1
你的编译器没有提示吗?没有“从整数中创建指针”的警告吗? - Gerhardh
没有警告,编译成功了 :) - Azeem Muzammil
5个回答

6

在您的第一段代码中:

int main() {
    int a = 12;
    int *p;
    *p = a;
}

您遇到了严重的“未定义行为”问题,因为您试图将变量a的值赋给当前指向的p int变量。然而,p还没有被分配一个“地址”,因此它将具有任意 - 以及无效的值!一些编译器可能会将p初始化为零(或NULL),但这仍然是一个无效的地址(在大多数系统上)。
您的第二个代码片段是“正确”的,但是,按照目前的状态,它实际上并没有实现任何东西:
int main() {
    int a = 12;
    int *p;
    p = &a;
}

这里,你正在为指针变量 p 分配一个值(即一个地址);在这种情况下,p现在指向变量 a(也就是说,它的值是变量 a地址)。

因此,如果您将以下代码附加到第二段代码的末尾:

*p = 42;

如果您打印a的值,您将看到它的值已从最初给定的12更改为42

如需进一步澄清和/或解释,请随时提出。


在我的编译器(clang)中,在第一种情况下,如果我打印出来(printf("The address is %p\n", p)),它会打印一个地址。那里发生了什么? - Azeem Muzammil
3
正如我在回答中所说的,打印出来的“地址”将是一个任意的(有点随机)值。每次运行代码时,它可能会打印不同的“地址”。但那并不是有效的地址(即它不指向有效的内存块)。 - Adrian Mole

3

声明*pa会在内存中保留一些空间,第一种情况是为指针保留空间,在第二种情况下是为a(一个int)保留空间。

在这两种情况下,如果您不放入任何内容,则它们的值未被初始化。那并不意味着里面什么也没有,因为这是不可能的。这意味着它们的值是不确定的,有点像“随机”;加载程序在请求时将代码/数据放入内存中,并且由p占用的空间以及由a占用的空间都是加载时内存中存在的内容(也可以是编译时,但无论如何都是不确定的)。

因此,在第一种情况下执行*p = a会有很大的风险,因为您要求处理器将a“内部”的字节存储在p指向的任何位置。它可能在数据段的边界内,在堆栈中,在不会引起立即问题/崩溃的某个位置,但可能性很是不行的!

这就是为什么这个问题被称为“未定义行为”(UB)的原因。


1
当您初始化指针时,可以使用*p访问所指变量的指针值而不是指向所指变量的地址,但不能像*p = a这样影响值。因为您试图在没有变量地址的情况下影响值。
第二段代码正确使用p =&a。

1
第一个是不好的:
int main() {
    int a = 12;
    int *p;
    *p = a;
}

这意味着:将变量a的值放入指针p所指向的位置。但p指向什么?可能没有指向任何东西(NULL)或任何随机地址。在最好的情况下,它可能会导致执行错误,例如访问冲突或分段错误。在最坏的情况下,它可能会覆盖完全未知变量的任何现有值,从而导致非常难以调查的问题。

第二个没问题。

int main() {
    int a = 12;
    int *p;
    p = &a;
}

这意味着:获取指向(已存在的)变量a的指针,并将其分配给指针p。因此,这将正常工作。

“但是p指向什么?什么都没有(NULL)”很有可能它只是指向与“NULL”不同的某个地方。 - alk
根据C标准,只有全局或静态变量(任何类型)在第一次使用之前才会被隐式初始化,其他所有变量都是猜测。我不会猜测。 - alk

1
区别在于,将变量的地址分配给指针变量是前提条件,而解除引用则是实现指针解除引用的步骤。它们是为了实现指针解除引用的好处而采取的不同步骤。

为了解释它们之间的差异,我们必须分别看看这些人:


  • 指针解引用是什么?

首先我们需要了解什么是引用。引用是一个对象的标识符,例如我们可以说“变量a代表着值12”,因此a是指向12值的引用。

对象的标识符是存储在其中的值的引用。

指针也是一样的。指针就像普通的对象一样,它们内部存储一个值,因此它们将这些存储的值作为引用。

“解引用”是当我们“禁用”与通常值之间的连接并使用p的标识符来访问/引用与p中存储的值不同的另一个值时发生的。

“解引用指针”意味着您使用指针访问存储在另一个对象中的值,例如在a中的12,而不是通过其自身的标识符a

要解引用指针,需要在指针变量之前加上*解引用运算符,如*p


  • 什么是将变量地址分配给指针?

通过将指针的值设置为另一个对象的地址,类似于给普通变量赋值的方式,我们实现了“什么是解引用指针?”中所述的内容。

但与通常的对象初始化/分配不同,我们需要使用&符号运算符,在变量之前,其值应该由指针指向,而*解引用运算符则必须省略,如下所示:

  p = &a;

随后,指针“指向”存储所需值的地址。

正确取消引用指针的步骤:

首先要做的是声明一个指针,例如:

 int *p;

在这种情况下,我们声明一个指向类型为int的对象的指针变量p

第二步是使用类型为int的对象的地址值初始化指针:

 int a = 12;
 p = &a;       //Here we assign the address of `a` to p, not the value of 12.

注意:如果您想获取对象的地址值,就像通常变量那样,您需要使用一元运算符&,在对象之前。
如果您已经完成这些步骤,最终可以通过在指针对象前使用*运算符来访问指针所指向的对象的值。
     *p = a;

我是一名有用的助手,可以翻译文本。
我的问题是这两个代码之间有什么区别?
区别就在于第一个代码段:
int main() {
    int a = 12;
    int *p;
    *p = a;
}

通过解引用指针寻址对象是无效的。如果没有先进行一次引用来确定指针所指向的对象,就不能给指针的解引用赋值。

因此,你的假设:

在第一段代码中,我将指针解引用为 *p = a...

是错误的。

在这种情况下,你不能以正确的方式使用 *p = a 解引用指针,因为指针 p 没有任何引用,你无法正确地对指针进行解引用。

实际上,使用语句 *p = a 将值 a 分配到内存的某个位置。

通常,编译器不应该让这样的代码通过而不报错。

如果发生了这种情况,并且你稍后想要使用指针正确地分配的值,例如 printf("%d",*p),你将会得到一个 Segmentation fault (core dumped) 错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接