给指针赋值

4
我对指针的概念有些困惑,其中之一就是:
假设我们声明了一个整型变量n和一个指向它的指针*p。
int n=23,*p;
现在,如果我没记错的话,p=&n;将变量n的地址(比如3000)赋给指针p。
所以,cout<<p<<" "<<*p;会依次输出3000和23。
我的疑问是,如果我们像这样做:
p=5;把一个用于存储内存位置的变量赋予一个数值,会发生什么?
变量是否移动到内存位置“5”(很可能不是),或者指针是否只是转换成一个“int”,并被赋值为5?我本来想自己尝试一下,但玩弄系统内存让我有所顾虑。
此外,在我们声明任何变量(比如2个字节大小的整型变量)时,它是否存储在随机内存位置,比如3000、101、2700之类,还是存储在0、2、4等位置?接下来声明的变量是否直接存储在相邻位置(比如3002、103或2702),还是在它们之间存在某种间隔?

6
你无法这样做,“5”不能转换为“int”。(如果你真的尝试过*,就可能会发现这一点。) - Kerrek SB
1
没问题,很安全,你可以在你的机器上测试它。通过犯错误来学习,这是最好的方式。你会得到一个漂亮的编译器错误。 - YSC
2
@KerrekSB 实际上,他可以手动完成,写成 p = (int*)5; - alexeykuzmin0
3
如果您知道正确类型的有效地址的价值,那就没问题。如果您使用不同类型的地址或只是无效地址,则会出现未定义行为。我观看了一个讲座,他们使用现代C++来在Commodore 64上编程乒乓球,由于它使用内存映射设备,所以他们不得不这样做。 - NathanOliver
1
@alexeykuzmin0 实际上,我认为intptr_t是适当的标准类型。 - NathanOliver
显示剩余4条评论
5个回答

4
在您的例子中,这是一个编译错误。但是我认为您想要做的是这样的:
int n =23, *p;
p = &n;
//Change the value of p to 3000, p now points to address 3000
p = reinterpret_cast<int*>(3000); 
//Check if the address of n has changed
std::cout << "Address of n : " << reinterpret_cast<int>(&n) << std::endl; 

当您运行此代码时,可以看到n的地址不会改变。

至于您的第二个问题:

是和不是 :)

如果您定义了两个相邻的变量,它们可能会在内存中相邻。

 int a,b,c,d;
 char c = 1;
 short s = 1;
 void* p = nullptr;
 int i = 1;

 std::cout << "a is at: " << reinterpret_cast<int>(&a) << std::endl;
 std::cout << "b is at: " << reinterpret_cast<int>(&b) << std::endl;
 std::cout << "c is at: " << reinterpret_cast<int>(&c) << std::endl;
 std::cout << "d is at: " << reinterpret_cast<int>(&d) << std::endl;
 std::cout << "Char is at: " << reinterpret_cast<int>(&c) << std::endl;
 std::cout << "Short is at: " << reinterpret_cast<int>(&s) << std::endl;
 std::cout << "Pointer is at: " << reinterpret_cast<int>(p) << std::endl;
 std::cout << "Int is at: " << reinterpret_cast<int>(&i) << std::endl;

这种行为是由编译器决定如何排列所有内容造成的。它们可能相邻也可能不相邻。如果你想确保它们相邻存在,请使用数组。

int arr[] = {1,2,3,4,5,6,7};
int * p = &arr[0]; //get address of first element
for(int i = 0 ; i < 7; ++i)
    std::cout << "Value at address: " << reinterpret_cast<int>(p+i) 
        << " is: " << *( p + i) << std::endl;

3
变量在存储时(相对于彼此的位置)是由编译器而不是操作系统决定的。 - melpomene
@melpomene 我的错误,已修复。 - user3853544
尽管操作系统将决定在内存中为程序分配哪个空间以进行自动存储。 - NathanOliver
我真傻,把虚拟和物理搞混了。 - user3853544
1
它还取决于内存分配单元;或是记忆体储存分类器的类型,例如:全域(global)堆叠或本地(stack or local)来自堆(heap)静态(static)。还有其他的,但这四种最常见的内存转换单元取决于编译器、操作系统和您的系统架构。我包括所有三个原因是每个编译器处理它们的堆栈帧和堆的方式不同,每个操作系统管理或访问它们的方式不同,甚至架构可能以小端和大端等不同方式存储它们。还需要考虑易失性内存和可变内存。 - Francis Cugler
(继续)但大部分关于堆栈和堆构建和工作方式的语义是由编译器和编译器优化隐藏的;当你直接使用汇编语言(ASM)时,你仍然需要对它们有所了解和注意。只有在这种情况下,它才是非常重要的事情。 - Francis Cugler

2
您可以将int转换为指针。这是标准的。标准保证,如果您将指针转换回int,则应该获得原始值,前提是在两者之间没有发生截断。
但是解引用此类指针是未定义行为。这意味着在常见的实现中,如果尝试读取未映射或仅写入地址,您将获得段错误或内存违规,或者仅仅因为您读取了一个位置而获得不可预测的值,而您不知道那里有什么...
如果您在那里写入,情况甚至更糟,因为您可能会覆盖程序中的随机位置。想象一下当您覆盖函数的返回地址时会发生什么...
唯一真正的用例是当某些特殊硬件寄存器映射到众所周知的地址时。然后您实际上会写:
char *p;
p = 0x60;    // say 0x60 is the address of a special register you want to read
char reg_value = *p;

这无法被标准定义,因为标准不对底层平台做任何假设,但是它可以作为本地扩展来记录在特定的硬件平台上。


2
假设您添加了一个转换并使其编译通过
p=(int *)5;

那么对该指针解引用将是未定义的行为。它可以做任何事情,但很可能您的程序会中止。在不同的系统上,它的行为可能会有所不同。

此外,变量或下一个变量的地址也取决于您运行程序的系统。大多数系统使用堆栈存储局部变量,但它们可以向上或向下计数,因此您不知道确切位置。

调试和优化版本也可以生成不同的代码。有些编译器在本地数组或变量之后留有空间,以便在您写出数组外部时给出有意义的错误提示。但在发布版本中,它们可能尽可能紧密地使用内存。

更加不可预测的是,主流操作系统采用一种称为地址空间布局随机化(ASLR)的技术。

https://en.wikipedia.org/wiki/Address_space_layout_randomization

变量的基地址将在每次运行程序时随机化。这样,黑客就无法滥用缓冲区溢出并计算返回地址,他们也无法注入自己的代码了。
因此,你不能对变量地址做出一般性的说法。你需要检查你的编译器和操作系统。

1
让我们用一个类比来说明……
int  x = 3;
int* x_p = &x;

你可以这样理解:现在你有一张纸,叫做x,上面写着数字3。你还有另一张纸,上面写着第一张纸的位置。具体实现方式并不重要,所以我们假设第二张纸叫做x_p,上面写着x。按照这种方式,
int y = *x_p;

意思是:看看纸张 x_p,把它解释为另一张纸的位置,并取出写在那张纸上的值,即y将具有值3。现在,如果你在纸张x_p上写了其他东西,会发生什么?首先,如果您仍然尝试将其解释为另一张纸的位置,您将失败,或者只会得到一张随机的纸张,但不是您要找的那张。其次,这对第一张纸x有什么影响吗?它根本没有受到影响。指针只是像任何其他变量一样的变量,只是通常将它们的值解释为某些其他变量的位置,但否则指针和所指对象之间没有任何联系。这不是最好的比喻,但也许有帮助。

-1

是的,你是对的 & 是一个地址运算符。它会返回一个指针,指向哪个地址。

如果你在 p 前加上星号,那么就意味着你想要从该指针所指向的位置获取值。

整数从内存中占用 4 字节,所以每次创建整数时都会从内存堆栈中占用 4 字节。它可以按照递增或递减的顺序排列。

你也可以在你的 IDE 上尝试所有这些。

int n = 23,*p;
int n2 = 24;
int n3 = 25;

p = &n;
printf("%d %d %d %d %d",p,n,*p,&n2,&n3);

1
%d 期望一个 int。传递一个 int * 的行为是未定义的。此外,int 不一定需要占用4个字节。 - melpomene
抱歉,我的意思不是每个int都占用4个字节。这只是默认32位整数的一个例子。 - hunterTR

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