如何在 C 中使用内存地址?它们是十六进制数还是无符号整数?
How to work with memory addresses in C? Are they hexadexcimals or unsigned ints?
我正在学习 C,最近 class 研究了指针和内存地址。
老师告诉我们,内存位置基本上都是无符号数,所以我们可以用下面的代码显示:
int a;
printf("%u", &a);
或
int a, *p=&a;
printf("%u", p);
的确,这行得通,但我还在一些论坛上看到应该使用 %p 或 %x 来打印地址。所以,它们一定是十六进制数……我在上面看到的整数实际上是十六进制数转换为十进制数吗?什么是基本形式的地址 - 十六进制或整数或简单的二进制数。
请帮我解决这个问题。
基本形式的地址只是值。十六进制、二进制或八进制是数字的表示。例如,42
、0x2a
、2a<sub>16</sub>
、052
、52 <sub>8</sub>
、101010<sub>2</sub>
和 10<sub>42</sub>
都是相同值的不同表示。
就格式字符串的使用而言,%p
是正确的。无法保证无符号整数与指针具有相同的位数,因此您很可能会丢失信息。
事实上,向 printf
提供与相应格式说明符不匹配的参数实际上是未定义的行为。
使用 %u
或 %x
变体可能适用于指针和无符号整数具有兼容大小但真正可移植代码不依赖于此的大多数系统。例如,一个实现可以自由地拥有一个 16 位无符号整数(以满足 ISO C11 Appendix E
中的最小范围要求)和一个 1024 位指针类型(我必须得到 之一 台机器)。
在计算机中,如果你仔细观察,一切都是数字:)
指针确实是一个数字。唯一的问题是,这个数字的大小取决于硬件架构和内存模型。
例如,一些处理器使用 64 位地址,而微控制器可能只使用 16 位。
即使在 64 位处理器上,给定的进程也可能仅限于 32 位地址(通常是 WIN32 架构,可以 运行 在 64 位处理器上)。
如果您尝试使用 %u 打印指针,则无法保证整数的大小等于指针的大小。通常在 WIN64 架构上 %u 只会显示指针的前 32 位。
另一方面,%p 格式考虑了实际指针大小并保证其值将正确显示。
打印对象地址的可移植方式是
int a, *p=&a;
printf("%p", (void *) p);
"%p"
说明符匹配 void*
。 C 没有为 int *
提供 printf()
说明符,但所有对象指针都可以转换为 void*
.
许多体系结构使用从 0 开始到可能超过 INT_MAX
的某个大值的线性地址。然而 C 提供了许多地址方案。上面的输出可以是下面的任何一个,不一定只由数字字符组成:
123456789
0x0001C800
1000:0008
int:0000
这是在 C 中执行此操作的标准方法:
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int x = 0;
printf( "The value of &x is %" PRIxPTR " in hex and %" PRIuPTR
" in decimal, and %%p displays %p.\n",
(uintptr_t)&x,
(uintptr_t)&x,
(void*)&x);
return EXIT_SUCCESS;
}
uintptr_t
是一个整数,可以安全地存储指向对象的指针。
以及 C++ 解决方案:
#include <climits>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
using std::cout;
using std::endl;
using std::resetiosflags;
using std::setfill;
using std::setiosflags;
using std::setw;
using std::streamsize;
int main(void) {
int x = 0;
static const streamsize hex_width = static_cast<streamsize>(sizeof(int*)*CHAR_BIT/4);
static const streamsize dec_width = 0; // Don't pad.
cout << "The value of &x is " << resetiosflags(cout.basefield) << setw(hex_width) << setfill('0')
<< setiosflags(cout.hex) << reinterpret_cast<uintptr_t>(&x) << " in hex, "
<< resetiosflags(cout.basefield) << setw(dec_width)
<< setiosflags(cout.dec) << reinterpret_cast<uintptr_t>(&x) << " in decimal, and "
<< resetiosflags(cout.basefield) << static_cast<void*>(&x) << " as a pointer." << endl;
return EXIT_SUCCESS;
两者都对。
试试这个代码:
int a = 10;
printf("%p\n%u", &a, &a);
将以十六进制 ( %p ) 和 uint ( %u ) 形式打印指针地址。
如果将hex转成uint,在开发者模式下使用windows的calc,
你将拥有相同的价值。
读这个:https://en.wikibooks.org/wiki/C_Programming/Pointers_and_arrays
喝咖啡继续写代码! =)
学习C,每个人都要跨过的一道坎就是交友指点。在 C 中,与其他语言不同,您是在非常低的机器级别上进行编码,并且您在 C 中所做的几乎所有事情都是内存操作。学习C的关键是知道"What is at that memory address?"、"How big of a block of memory do I have?"和"How do I work with it correctly?"。不幸的是,很多时候人们使这个过程比需要的更难。
一件真正有帮助的事情,也是理解大量低级编程的基础,就是所有变量只不过是一个 标签,或者更好,内存地址的 别名 。
普通变量和指针变量的唯一区别在于,它不是 别名 指向某些 直接 或 立即值存储在内存中(如5
或21
),指针是指向内存位置的别名 其他东西的地址被存储。简单地说,指针存储一个内存地址作为它的值。
这样想基础:
int x = 5;
什么是x
?它是一个 内存地址的标签 。 x
标记(别名)的内存地址是什么? &x
。该地址存储了什么? 5
。目前还好吗?
什么是指针?
int *y; /* what's so special about the '*' in the declaration?
Nothing, just basic syntax for pointer declaration. */
y = &x; /* which stores the address of x as the value of y.
And what is x? -- a label to a memory location */
什么是y
?它是一个 内存地址的标签 。 y
标记(别名)的内存地址是什么? &y
。该地址存储了什么? &x
(x
的地址)。还好吗?
然后让我们看一个简短的例子,希望能帮助巩固基础知识,这样你就可以继续真正按预期使用指针,访问和操作内存块,如数组、链表、堆栈等。 ..:[=43=]
#include <stdio.h>
void prn_values (int val, int *ptr);
int main (void) {
int x = 5; /* declare variable & pointer */
int *y = &x; /* y now holds the address of x */
prn_values (x, y);
int z = 7;
y = &z; /* y now holds the address of z */
prn_values (z, y);
z = x; /* y still holds the value of z, */
prn_values (z, y); /* but the value of z has changed */
*y = 9; /* y still holds z, but we have changed z by */
/* changing the value at the address stored in y */
printf ("\n z : %-14d (value)\n &z : %-14p (address)\n", z, &z);
/* actually using a pointer to an array */
char array[] = "pointer arithmetic.";
char *p = array; /* p points to the start address of array */
printf ("\n array : %p (address)\n array [0]: %p (address)\n\n",
array, &array[0]);
while (*p) {
printf (" %c : %p\n", *p, p);
p++;
}
return 0;
}
void prn_values (int val, int *ptr)
{
printf ("\n x : %-14d (value)\n &x : %-14p (address)\n", val, &val);
printf (" y : %-14p (value)\n &y : %-14p (address)\n *y : %-14d "
"(dereference)\n", ptr, &ptr, *ptr);
}
编译
gcc -Wall -Wextra -o pointers pointers.c
输出
下面给整型指针y
赋值x
,然后z
输出指针所在地址的值、地址和值。 注意指针的值是它指向的变量的地址。当 y
指向 z
时, z
的值发生了变化。由于 y
保存(指向)由 z
别名的内存地址,*y
也反映了变化。然后注意变化是如何双向起作用的。如果 y
持有的地址的值通过赋值更改为 *y
,该更改也会反映在 z
中。
这就是所有基本的指针基础知识,几乎没有展示指针的真正功能和用途。它是 指针算法 并且能够将指针作为函数参数传递,其中可以看到指针的使用和值。请注意最后的 while 循环如何使用指向字符数组的指针 p
来访问数组中的每个地址(保存一个字符),无非就是将 p
递增 1(p++;
) 在每次迭代期间。
$ ./pointers
x : 5 (value)
&x : 0x7ffff2c72590 (address)
y : 0x7ffff2c72590 (value)
&y : 0x7ffff2c725b0 (address)
*y : 5 (dereference)
z : 7 (value)
&z : 0x7ffff2c725a0 (address)
y : 0x7ffff2c725a0 (value)
&y : 0x7ffff2c725b0 (address)
*y : 7 (dereference)
z : 5 (value)
&z : 0x7ffff2c725a0 (address)
y : 0x7ffff2c725a0 (value)
&y : 0x7ffff2c725b0 (address)
*y : 5 (dereference)
z : 9 (value)
z : 0x7ffff2c725a0 (address)
array : 0x7ffff2c725c0 (address)
array [0]: 0x7ffff2c725c0 (address)
p : 0x7ffff2c725c0
o : 0x7ffff2c725c1
i : 0x7ffff2c725c2
n : 0x7ffff2c725c3
t : 0x7ffff2c725c4
e : 0x7ffff2c725c5
r : 0x7ffff2c725c6
: 0x7ffff2c725c7
a : 0x7ffff2c725c8
r : 0x7ffff2c725c9
i : 0x7ffff2c725ca
t : 0x7ffff2c725cb
h : 0x7ffff2c725cc
m : 0x7ffff2c725cd
e : 0x7ffff2c725ce
t : 0x7ffff2c725cf
i : 0x7ffff2c725d0
c : 0x7ffff2c725d1
. : 0x7ffff2c725d2
指针运算适用于所有数组类型(char、int、struct foo 等)。通过声明 type X
的指针,编译器知道数据类型 X
的大小。通过指针运算,复杂数据类型的访问和迭代没有什么不同。指向函数的指针和指向指针的指针的工作方式相同。随着您获得更多经验,您将开始依赖指针作为您的 C 工具箱的很大一部分,因此花时间彻底了解基础知识是非常值得的。将指针作为函数参数传递是 C 中指针使用方式的另一个完整方面。由于这个答案比最初预期的要长得多,我们将把它留到另一天。如果您有任何问题,请告诉我。祝C.好运
我正在学习 C,最近 class 研究了指针和内存地址。 老师告诉我们,内存位置基本上都是无符号数,所以我们可以用下面的代码显示:
int a;
printf("%u", &a);
或
int a, *p=&a;
printf("%u", p);
的确,这行得通,但我还在一些论坛上看到应该使用 %p 或 %x 来打印地址。所以,它们一定是十六进制数……我在上面看到的整数实际上是十六进制数转换为十进制数吗?什么是基本形式的地址 - 十六进制或整数或简单的二进制数。
请帮我解决这个问题。
基本形式的地址只是值。十六进制、二进制或八进制是数字的表示。例如,42
、0x2a
、2a<sub>16</sub>
、052
、52 <sub>8</sub>
、101010<sub>2</sub>
和 10<sub>42</sub>
都是相同值的不同表示。
就格式字符串的使用而言,%p
是正确的。无法保证无符号整数与指针具有相同的位数,因此您很可能会丢失信息。
事实上,向 printf
提供与相应格式说明符不匹配的参数实际上是未定义的行为。
使用 %u
或 %x
变体可能适用于指针和无符号整数具有兼容大小但真正可移植代码不依赖于此的大多数系统。例如,一个实现可以自由地拥有一个 16 位无符号整数(以满足 ISO C11 Appendix E
中的最小范围要求)和一个 1024 位指针类型(我必须得到 之一 台机器)。
在计算机中,如果你仔细观察,一切都是数字:)
指针确实是一个数字。唯一的问题是,这个数字的大小取决于硬件架构和内存模型。
例如,一些处理器使用 64 位地址,而微控制器可能只使用 16 位。
即使在 64 位处理器上,给定的进程也可能仅限于 32 位地址(通常是 WIN32 架构,可以 运行 在 64 位处理器上)。
如果您尝试使用 %u 打印指针,则无法保证整数的大小等于指针的大小。通常在 WIN64 架构上 %u 只会显示指针的前 32 位。
另一方面,%p 格式考虑了实际指针大小并保证其值将正确显示。
打印对象地址的可移植方式是
int a, *p=&a;
printf("%p", (void *) p);
"%p"
说明符匹配 void*
。 C 没有为 int *
提供 printf()
说明符,但所有对象指针都可以转换为 void*
.
许多体系结构使用从 0 开始到可能超过 INT_MAX
的某个大值的线性地址。然而 C 提供了许多地址方案。上面的输出可以是下面的任何一个,不一定只由数字字符组成:
123456789
0x0001C800
1000:0008
int:0000
这是在 C 中执行此操作的标准方法:
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int x = 0;
printf( "The value of &x is %" PRIxPTR " in hex and %" PRIuPTR
" in decimal, and %%p displays %p.\n",
(uintptr_t)&x,
(uintptr_t)&x,
(void*)&x);
return EXIT_SUCCESS;
}
uintptr_t
是一个整数,可以安全地存储指向对象的指针。
以及 C++ 解决方案:
#include <climits>
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
using std::cout;
using std::endl;
using std::resetiosflags;
using std::setfill;
using std::setiosflags;
using std::setw;
using std::streamsize;
int main(void) {
int x = 0;
static const streamsize hex_width = static_cast<streamsize>(sizeof(int*)*CHAR_BIT/4);
static const streamsize dec_width = 0; // Don't pad.
cout << "The value of &x is " << resetiosflags(cout.basefield) << setw(hex_width) << setfill('0')
<< setiosflags(cout.hex) << reinterpret_cast<uintptr_t>(&x) << " in hex, "
<< resetiosflags(cout.basefield) << setw(dec_width)
<< setiosflags(cout.dec) << reinterpret_cast<uintptr_t>(&x) << " in decimal, and "
<< resetiosflags(cout.basefield) << static_cast<void*>(&x) << " as a pointer." << endl;
return EXIT_SUCCESS;
两者都对。 试试这个代码:
int a = 10;
printf("%p\n%u", &a, &a);
将以十六进制 ( %p ) 和 uint ( %u ) 形式打印指针地址。 如果将hex转成uint,在开发者模式下使用windows的calc, 你将拥有相同的价值。
读这个:https://en.wikibooks.org/wiki/C_Programming/Pointers_and_arrays
喝咖啡继续写代码! =)
学习C,每个人都要跨过的一道坎就是交友指点。在 C 中,与其他语言不同,您是在非常低的机器级别上进行编码,并且您在 C 中所做的几乎所有事情都是内存操作。学习C的关键是知道"What is at that memory address?"、"How big of a block of memory do I have?"和"How do I work with it correctly?"。不幸的是,很多时候人们使这个过程比需要的更难。
一件真正有帮助的事情,也是理解大量低级编程的基础,就是所有变量只不过是一个 标签,或者更好,内存地址的 别名 。
普通变量和指针变量的唯一区别在于,它不是 别名 指向某些 直接 或 立即值存储在内存中(如5
或21
),指针是指向内存位置的别名 其他东西的地址被存储。简单地说,指针存储一个内存地址作为它的值。
这样想基础:
int x = 5;
什么是x
?它是一个 内存地址的标签 。 x
标记(别名)的内存地址是什么? &x
。该地址存储了什么? 5
。目前还好吗?
什么是指针?
int *y; /* what's so special about the '*' in the declaration?
Nothing, just basic syntax for pointer declaration. */
y = &x; /* which stores the address of x as the value of y.
And what is x? -- a label to a memory location */
什么是y
?它是一个 内存地址的标签 。 y
标记(别名)的内存地址是什么? &y
。该地址存储了什么? &x
(x
的地址)。还好吗?
然后让我们看一个简短的例子,希望能帮助巩固基础知识,这样你就可以继续真正按预期使用指针,访问和操作内存块,如数组、链表、堆栈等。 ..:[=43=]
#include <stdio.h>
void prn_values (int val, int *ptr);
int main (void) {
int x = 5; /* declare variable & pointer */
int *y = &x; /* y now holds the address of x */
prn_values (x, y);
int z = 7;
y = &z; /* y now holds the address of z */
prn_values (z, y);
z = x; /* y still holds the value of z, */
prn_values (z, y); /* but the value of z has changed */
*y = 9; /* y still holds z, but we have changed z by */
/* changing the value at the address stored in y */
printf ("\n z : %-14d (value)\n &z : %-14p (address)\n", z, &z);
/* actually using a pointer to an array */
char array[] = "pointer arithmetic.";
char *p = array; /* p points to the start address of array */
printf ("\n array : %p (address)\n array [0]: %p (address)\n\n",
array, &array[0]);
while (*p) {
printf (" %c : %p\n", *p, p);
p++;
}
return 0;
}
void prn_values (int val, int *ptr)
{
printf ("\n x : %-14d (value)\n &x : %-14p (address)\n", val, &val);
printf (" y : %-14p (value)\n &y : %-14p (address)\n *y : %-14d "
"(dereference)\n", ptr, &ptr, *ptr);
}
编译
gcc -Wall -Wextra -o pointers pointers.c
输出
下面给整型指针y
赋值x
,然后z
输出指针所在地址的值、地址和值。 注意指针的值是它指向的变量的地址。当 y
指向 z
时, z
的值发生了变化。由于 y
保存(指向)由 z
别名的内存地址,*y
也反映了变化。然后注意变化是如何双向起作用的。如果 y
持有的地址的值通过赋值更改为 *y
,该更改也会反映在 z
中。
这就是所有基本的指针基础知识,几乎没有展示指针的真正功能和用途。它是 指针算法 并且能够将指针作为函数参数传递,其中可以看到指针的使用和值。请注意最后的 while 循环如何使用指向字符数组的指针 p
来访问数组中的每个地址(保存一个字符),无非就是将 p
递增 1(p++;
) 在每次迭代期间。
$ ./pointers
x : 5 (value)
&x : 0x7ffff2c72590 (address)
y : 0x7ffff2c72590 (value)
&y : 0x7ffff2c725b0 (address)
*y : 5 (dereference)
z : 7 (value)
&z : 0x7ffff2c725a0 (address)
y : 0x7ffff2c725a0 (value)
&y : 0x7ffff2c725b0 (address)
*y : 7 (dereference)
z : 5 (value)
&z : 0x7ffff2c725a0 (address)
y : 0x7ffff2c725a0 (value)
&y : 0x7ffff2c725b0 (address)
*y : 5 (dereference)
z : 9 (value)
z : 0x7ffff2c725a0 (address)
array : 0x7ffff2c725c0 (address)
array [0]: 0x7ffff2c725c0 (address)
p : 0x7ffff2c725c0
o : 0x7ffff2c725c1
i : 0x7ffff2c725c2
n : 0x7ffff2c725c3
t : 0x7ffff2c725c4
e : 0x7ffff2c725c5
r : 0x7ffff2c725c6
: 0x7ffff2c725c7
a : 0x7ffff2c725c8
r : 0x7ffff2c725c9
i : 0x7ffff2c725ca
t : 0x7ffff2c725cb
h : 0x7ffff2c725cc
m : 0x7ffff2c725cd
e : 0x7ffff2c725ce
t : 0x7ffff2c725cf
i : 0x7ffff2c725d0
c : 0x7ffff2c725d1
. : 0x7ffff2c725d2
指针运算适用于所有数组类型(char、int、struct foo 等)。通过声明 type X
的指针,编译器知道数据类型 X
的大小。通过指针运算,复杂数据类型的访问和迭代没有什么不同。指向函数的指针和指向指针的指针的工作方式相同。随着您获得更多经验,您将开始依赖指针作为您的 C 工具箱的很大一部分,因此花时间彻底了解基础知识是非常值得的。将指针作为函数参数传递是 C 中指针使用方式的另一个完整方面。由于这个答案比最初预期的要长得多,我们将把它留到另一天。如果您有任何问题,请告诉我。祝C.好运