有人可以帮助理解以下代码的输出是如何生成15 15的吗?
#include <stdio.h>
void foo(int **p1);
int main()
{
int i = 10;
int *p = &i;
foo(&p); //address of constant pointer p passed to function
printf("%d\n", *p);
}
void foo(int **p1)
{
int j = 15;
*p1 = &j; //I don't get this line
printf("%d\n", **p1);
}
让我们假设addr(i)= ff2,addr(指针p)= ff4,addr(j)= ff6
i = 10,j = 15
p =地址(i),所以p = ff2
p1 =地址(p),所以p1 = ff4
答案 0 :(得分:1)
程序有不确定的行为。
要理解程序中的错误,让我们为指针类型引入一个typedef。
这是一个示范程序。
#include <stdio.h>
typedef int * T;
int main(void)
{
int i = 10;
const T p = &i;
printf( "%d\n", *p );
return 0;
}
程序输出
10
正如所见,变量p
是用限定符const。
现在让我们尝试添加函数foo
。
#include <stdio.h>
typedef int * T;
void foo( T *pp )
{
}
int main(void)
{
int i = 10;
const T p = &i;
foo( &p );
printf( "%d\n", *p );
return 0;
}
糟糕,编译器发出错误
prog.c:15:7: error: passing argument 1 of ‘foo’ discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
foo( &p );
^
prog.c:5:6: note: expected ‘int **’ but argument is of type ‘int * const*’
void foo( T *pp )
实际上变量p
是使用限定符const
const T p = &i;
因此,指向变量的指针必须具有类型const T *
。
#include <stdio.h>
typedef int * T;
void foo( const T *pp )
{
}
int main(void)
{
int i = 10;
const T p = &i;
foo( &p );
printf( "%d\n", *p );
return 0;
}
现在,如果要取消引用函数中的参数pp
,我们会得到一个可能无法更改的常量对象。
#include <stdio.h>
typedef int * T;
void foo( const T *pp )
{
int j = 15;
*pp = &j;
}
int main(void)
{
int i = 10;
const T p = &i;
foo( &p );
printf( "%d\n", *p );
return 0;
}
所以编译器会再次发出错误
prog.c: In function ‘foo’:
prog.c:8:6: error: assignment of read-only location ‘*pp’
*pp = &j;
^
此外,您正在尝试分配一个本地变量的地址,该地址变量在退出该函数后将无效。因此,即使指针在任何情况下都不是常量,程序也有不确定的行为。
编辑:
在你从指针const
的声明中删除p
限定符的问题中认真改变你的程序(这是一个坏主意)后,我已经说过程序有未定义的行为,因为指针已分配使用局部变量的值。因此,指针将具有无效值,因为退出函数后该变量不再存在,但您可以获得预期的结果。
void foo(int **p1)
{
int j = 15;
// Assigning the original variable p pointed to by the parameter p1
// the address of the local variable j.
*p1 = &j; //I don't get this line
printf("%d\n", **p1);
}
至于这句话
*p1 = &j;
然后指针p1指向原始指针p
。因此,取消引用指针p1
,您将获得分配了局部变量p
的地址的原始指针j
。
答案 1 :(得分:1)
忽略未定义的行为 1 一分钟,这是正在发生的事情,或者至少是该示例的 intent 。
首先,您有一个包含值i
的整数对象10
:
+----+
i: | 10 |
+----+
您创建指向p
的指针对象i
:
+---+ +----+
p: | | ----> i: | 10 |
+---+ +----+
您将p
的地址传递给foo
。函数参数p1
指向p
:
+---+ +---+ +----+
p1: | | ----> p: | | ----> i: | 10 |
+---+ +---+ +----+
在foo
内,您可以使用值j
创建整数对象15
:
+----+
j: | 15 |
+----+
现在,这是有趣的部分 - 您通过取消引用 p
将j
设置为指向p1
,并将j
的地址指定给结果。 *p1 == p
,因此扩展名*p1 = &j
实际上与撰写p = &j
相同。在该行之后,您有以下情况:
+---+ +---+ +----+
p1: | | ----> p: | | ----> j: | 15 |
+---+ +---+ +----+
这就是为什么在**p1
输出foo
中输出p1 == &p
的值为*p1 == p == &j
,所以**p1 == *p == j == 15
,所以foo
。
现在,当函数j
退出时,p
不再存在,因此j
不再是有效指针 - 它不再指向活动对象。由于它不再有效,因此取消引用它的行为是 undefined ;您的代码可能会崩溃,或者它可能会损坏数据,或者它可能看起来像预期的那样工作,或者完全做其他事情。
逻辑对象15
不再存在,但它曾经占用的内存仍然存在,并且将包含写入它的最后一个东西,即值{{1 }}。只要没有其他内容覆盖该内存,您将继续看到*p == 15
。但是这种行为并不能保证,你不应该依赖它。
正如弗拉德和其他人所指出的那样,这远远不是这个代码的唯一问题。但同样,这基本上就是发生了什么。
<小时/>
foo
(函数返回int
)的隐式声明与{的明确定义之间的类型不匹配{1}}(函数返回foo
),尝试通过非void
表达式写入const
- 限定指针并解除引用无效指针。