为什么我无法打印出这个指针的值?

6

昨天的问题之后,我进一步尝试了指针。具体来说是int (*) [n]类型的指针。

这是我写的一些代码:

#include <stdio.h>

int main(void)
{
    int a[5] = {1, 2, 3, 4, 5};

    int (*p) [5] = &a;
    int *q = a;

    printf("\t\t\t\t\tp\t\tq\n\n");
    printf("Original Pointers: \t%20d%20d", p, q);
    printf("\n\n");
    printf("Incremented Pointers:\t%20d%20d", p+1, q+1);
    printf("\n\n");
    printf("Pointer values:         %20d%20d", *p, *q);
    printf("\n\n");

    return 0;
}

以下是它的输出:

                                    p                 q

Original Pointers:             132021776           132021776

Incremented Pointers:          132021796           132021780

Pointer values:                132021776                   1
  • 当指针p递增时,每次跳过20个位置。这是因为它是int(*)[5]类型的指针,因此在数组中跳过sizeof(int) * number of columns

  • 无论pq具有相同的值(但不同的类型),但是当使用间接操作符与p一起使用时,我没有得到数组第一个单元格的值,而是得到了p本身的值。为什么会这样?

  • 当我使用int *p = &a时,它会引发警告(因为指针类型不同),但稍后当我使用间接操作符与p一起使用时,我可以打印出数组中第一个单元格的值。这是因为当我将&a赋值给p时,它将&arr的类型(即int (*) [5])转换为int *类型,然后再分配给p吗?


1
你的代码存在未定义行为;你必须使用“%p”格式说明符打印地址,并将参数转换为“void *”。 - ad absurdum
侧记:在打印指针时不要使用“%d”,因为int类型可能对于指针来说太小了(并且在典型的64位系统上确实是如此)。 - user2371524
gcc对于简单的赋值约束违规给出非常差的诊断结果。请确保使用gcc -std=c11 -pedantic-errors进行编译,以使其正常运行。 - Lundin
@Lundin 奇怪的是,我在使用gcc(版本4.9.2)时使用-pedantic-errors选项,并且使用%d打印指针时没有显示任何错误。我该如何解决这个问题? - Nathu
@NathuramGoatse 我认为它会发出警告,无论 -pedantic-errors 是否存在。我在版本 4.9.1 中进行了测试:warning: format '%d' expects argument of type 'int', but argument 2 has type 'int *' [-Wformat=]。无论我是否添加 -Wall -Wextra -pedantic-errors,我都会收到此警告。 - Lundin
@Lundin,我的代码没有显示任何错误。然而,使用-Wall选项似乎可以解决问题。谢谢。 - Nathu
3个回答

4
指针p在递增时跳过20。这是因为它是int(*)[5]类型的指针,因此按照数组中列数的sizeof(int) *数量跳过。

是的。

p和q的值相同(但类型不同),然而当使用间接操作符与p一起使用时,我没有得到数组第一个单元格中的值,而是得到了p的值。为什么?

p指向一个数组,所以用* p可以访问这个数组。不带索引评估数组会得到指向其第一个元素的指针。在此处,您的*p被评估为int *类型。

顺便说一下,*通常称为dereference操作符。使用不同的术语可能会使其他人感到困惑。
当我使用int *p = &a时,会出现警告(因为指针类型不同),但是当我使用间接操作符与p一起使用时,我可以得到打印数组第一个单元格的值。这是因为当我将&a分配给p时,它将int(*)[5]的类型转换为int*类型,然后将其分配给p吗?
我曾在这里回答错误,将其误读为类似于int *q = (int *)p的东西,这将创建一个别名指针并可能导致未定义的行为,因此在这里留下这个小提示,以防万一有人会想出这个主意。但是你在这个问题中提出的建议只是无效的分配。Lundin's answer对此进行了完整的解释。
你的代码存在更多问题,指针的打印应该像这样:

printf("Original Pointers: \t%20p%20p", (void *)p, (void *)q);

指针可以比 int 更大。需要将其转换为 void * 的原因是因为 printf() 是一种可变参数函数。如果它被声明为接受 void *,则转换将是隐式的,但由于它没有被声明为这样,您必须自己进行转换。

我正在学习K.N.King的《C程序设计——现代方法》一书中的C语言,并将*符号称为“间接”运算符。 - Nathu
在C标准中,它没有明确的名称,被描述为“一元*运算符表示间接引用”,因此使用该名称是有一定理由的,但我仍然认为这是一个不好的想法,因为它执行的操作通常被称为指针解引用,所以大多数C程序员会直接称其为解引用 - user2371524
1
int *p = &a 不是一个别名问题,它是无效的 C 代码。我写了一个解释为什么的答案。如果程序员通过强制转换进行指针转换,那么这将成为一个别名问题。但这里并非如此。指针转换不会隐式发生。 - Lundin
@FelixPalmen 他需要写 int *p = (int*)&a;。然后代码就可以编译了。我不太明白它如何调用未定义的行为。请注意,数据的_effective type_是int[5],因此通过int*访问该数组的项的代码不违反严格别名。像int* p = (int*)(bananas_t*)(double*)&a; .. *p = something;这样的代码就严格别名而言是可以的。 - Lundin
@Lundin 好的,我想我在这个问题上误读了,并且一直在考虑像 int *q = p 这样的东西...不同的事情 - 现在更改整个部分! - user2371524
显示剩余3条评论

3

您的代码导致 未定义行为,因此无法验证任何输出。

首先,一些通用信息:

根据标准,在使用格式说明符时,使用不匹配类型的参数会导致未定义行为。要使用printf()打印指针,必须使用%p格式说明符并将相应的参数转换为void *

相关的是,引用C11,第§7.21.6.1/P8章节

p

该参数应为指向void的指针。[...]

和P9,

[....]如果任何参数不是对应的转换说明符的正确类型,则其行为未定义。

由于printf()是可变参数函数,因此需要将其强制转换为void *


现在,让我们来谈谈更直接的问题。

§1. 当指针p递增时,跳跃20个单位。[...]

你是对的,检查数据类型。类型为int (*) [5]的指针将根据您的平台基于指向类型sizeof(int [5])进行递增/递减。指针算术遵守数据类型。

§2. pq具有相同的值(但类型不同),然而当使用间接运算符与`p [....]

请注意那里的类型。p的类型为int (*) [5],因此*p的类型为int [5]。就是这样。所有你应该得到的是一个数组作为解除引用的结果。(但请继续阅读.....

现在,当将数组类型作为函数参数传递时,它会衰减为指向数组第一个元素的指针,因此类似于int *,即指针。因此,最终您将打印指针值。
§3. 当我使用int *p = &a时,它会引发警告[...]。
等等。停止。这是一个约束违规。严格来说,这是无效的C代码。类型int *int (*) [5]不兼容,并且不存在使其成为有效表达式的转换(隐式或显式)。不要这样做,请使用适当的类型。

3
除了Felix的回答之外:
当我使用int *p = &a时,它会抛出一个警告。这是因为这段代码不是有效的C语言。它是所谓的约束违规(大致意思是严重的语言违规)。因此,编译器必须给出诊断消息。更好的编译器应该会给出错误而不是警告。
在C语言中,指针转换不会隐式进行,除非其中一个操作数是void*。
原因是表达式不是简单赋值的有效形式。有效的形式列在C11 6.5.16.1中。这里没有任何一种情况符合条件。
左操作数是未限定的指针类型。但是右操作数不是兼容的类型。因此,此条件未满足。没有void指针。也没有空指针常量。没有布尔值。

在约束违规的情况下,行为是否未定义(因为代码尽管存在违规但仍能正常工作)? - Nathu
@NathuramGoatse 是的,这样的程序完全不符合 C 标准,因为它已经不再是一个 C 程序,而是其他什么东西了。 - Lundin

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