当printf是变量的地址时,为什么要使用void*?

发布于 2024-12-03 01:08:46 字数 452 浏览 3 评论 0原文

我在 printf() 中看到了 (void*) 的一些用法。

如果我想打印一个变量的地址,我可以这样做吗:

int a = 19;
printf("%d", &a);
  1. 我想,&aa的地址,它只是一个整数,对吧?
  2. 我读过的许多文章都使用这样的内容:

    printf("%p", (void*)&a);
    

  1. %p 代表什么? (指针?)
  2. 为​​什么使用(void*)?我不能使用 (int)&a 来代替吗?

I saw some usage of (void*) in printf().

If I want to print a variable's address, can I do it like this:

int a = 19;
printf("%d", &a);
  1. I think, &a is a's address which is just an integer, right?
  2. Many articles I read use something like this:

    printf("%p", (void*)&a);
    
  1. What does %p stand for? (A pointer?)
  2. Why use (void*)? Can't I use (int)&a instead?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

单挑你×的.吻 2024-12-10 01:08:46

指针不是数字。它们在内部通常以这种方式表示,但它们在概念上是不同的。

void* 被设计为通用指针类型。任何指针值(函数指针除外)都可以转换为 void* 并再次转换回来,而不会丢失信息。这通常意味着 void* 至少与其他指针类型一样大。

printfs "%p" 格式需要类型为void*的参数。这就是为什么在该上下文中 int* 应该转换为 void* 的原因。 (没有隐式转换,因为它是一个可变参数;没有声明的参数,因此编译器不知道将其转换为什么。)

草率的做法,例如用 "%d" 打印指针,或传递从 int*printf 以及 "%p" 格式,在大多数当前系统上您可能可以摆脱这种情况,但它们使您的代码不可移植。 (请注意,在 64 位系统上,void*int 的大小很常见,因此使用 %d" 打印指针是 < em>确实不可移植,不仅仅是理论上。)

顺便说一句,"%p" 的输出格式是常见的实现定义的(无论是大写还是小写)。或没有前导"0x""0X"),但这并不是唯一的可能性,假设有一个合理的实现,这将是一个合理的方法。以人类可读的形式表示一个指针值(并且 scanf 将理解 printf 的输出)

。 code>int* 值是

printf("%p", (void*)&a);

不要偷懒解决办法并不难,

建议阅读:comp.lang.c FAQ 的第 4 部分。建议阅读:所有其他部分

编辑:

回应奥尔科特的问题:

还有一件事我不太明白。 int a = 10; int *p = &a;,所以p的值就是a在mem中的地址,对吗?如果正确,那么 p 的值范围为 0 到 2^32-1(如果 cpu 是 32 位),并且整数在 32 位操作系统上是 4 字节,对吗?那么p的值和整数有什么区别呢? p的值可以超出范围吗?

区别在于它们的类型不同。

假设一个系统上的 intint*void*float 都是 32 位(这是当前 32 位系统的典型情况)。 float 是 32 位这一事实是否意味着它的范围是 0 到 232-1?或者 -231 到 231-1?当然不是; float 的范围(假设 IEEE 表示)大约为 -3.40282e+38 到 +3.40282e+38,在整个范围内分辨率变化很大,加上负零、次规格化数、非规格化数、无穷大和 NaN 等奇异值(不是-a-数字)。 intfloat 都是 32 位,您可以获取 float 对象的 32 位并对其进行处理作为 int 表示,但结果与 float 的值没有任何直接关系。例如,int 的第二低位具有特定含义;如果为 0,则为值贡献 0;如果为 1,则为值贡献 2; float 的相应位具有含义,但它完全不同(它提供的值取决于指数的值)。

指针的情况非常相似。指针值有一个含义:它是某个对象的地址(或任何其他对象的地址,但我们现在将其放在一边)。在大多数当前系统上,将指针对象的位解释为整数,可以为您提供在机器级别上有意义的东西。但语言本身并不能保证、甚至暗示情况就是如此。

指针不是数字。

一个具体的例子:几年前,我遇到了一些代码,试图通过转换为整数来计算两个地址之间的字节差异。事情是这样的:

unsigned char *p0;
unsigned char *p1;
long difference = (unsigned long)p1 - (unsigned long)p0;

如果您假设指针只是数字,代表线性整体地址空间中的地址,那么这段代码就有意义。但该语言不支持该假设。事实上,有一个系统(Cray T90)打算运行该代码,但它根本无法运行。 T90 有 64 位指针指向 64 位字。字节指针是通过在指针对象的 3 个高位中存储偏移量在软件中合成的。以上述方式减去两个指针(如果它们的偏移量均为 0),将得到地址之间的数,而不是字节数。如果它们有非 0 偏移量,就会给你带来毫无意义的垃圾。 (从指针到整数的转换只会复制位;它可以完成为您提供有意义的字节索引的工作,但它没有。)

解决方案很简单:放弃强制转换并使用指针算术:

long difference = p1 - p0;

其他寻址方案也是可能的。例如,地址可能由(可能间接)引用内存块的描述符以及该块内的偏移量组成。

您可以假设地址只是数字,地址空间是线性且整体的,所有指针都具有相同的大小并具有相同的表示形式,指针可以安全地转换为 int 或转换为long,然后再次返回而不会丢失信息。基于这些假设编写的代码可能适用于大多数当前系统。但未来的某些系统完全有可能再次使用不同的内存模型,并且您的代码将被破坏。

如果您避免做出任何超出语言实际保证的假设,您的代码将更加面向未来。即使抛开可移植性问题,它也可能会更干净。

Pointers are not numbers. They are often internally represented that way, but they are conceptually distinct.

void* is designed to be a generic pointer type. Any pointer value (other than a function pointer) may be converted to void* and back again without loss of information. This typically means that void* is at least as big as other pointer types.

printfs "%p" format requires an argument of type void*. That's why an int* should be cast to void* in that context. (There's no implicit conversion because it's a variadic function; there's no declared parameter, so the compiler doesn't know what to convert it to.)

Sloppy practices like printing pointers with "%d", or passing an int* to printf with a "%p" format, are things that you can probably get away with on most current systems, but they render your code non-portable. (Note that it's common on 64-bit systems for void* and int to be different sizes, so printing pointers with %d" is really non-portable, not just theoretically.)

Incidentally, the output format for "%p" is implementation-defined. Hexadecimal is common, (in upper or lower case, with or without a leading "0x" or "0X"), but it's not the only possibility. All you can count on is that, assuming a reasonable implementation, it will be a reasonable way to represent a pointer value in human-readable form (and that scanf will understand the output of printf).

The article you read is entirely correct. The correct way to print an int* value is

printf("%p", (void*)&a);

Don't take the lazy way out; it's not at all difficult to get it right.

Suggested reading: Section 4 of the comp.lang.c FAQ. (Further suggested reading: All the other sections.

EDIT:

In response to Alcott's question:

There is still one thing I don't quite understand. int a = 10; int *p = &a;, so p's value is a's address in mem, right? If right, then p's value will range from 0 to 2^32-1 (if cpu is 32-bit), and an integer is 4-byte on 32-bit OS, right? then What's the difference between the p's value and an integer? Can p's value go out of the range?

The difference is that they're of different types.

Assume a system on which int, int*, void*, and float are all 32 bits (this is typical for current 32-bit systems). Does the fact that float is 32 bits imply that its range is 0 to 232-1? Or -231 to 231-1? Certainly not; the range of float (assuming IEEE representation) is approximately -3.40282e+38 to +3.40282e+38, with widely varying resolution across the range, plus exotic values like negative zero, subnormalized numbers, denormalized numbers, infinities, and NaNs (Not-a-Number). int and float are both 32 bits, and you can take the 32 bits of a float object and treat it as an int representation, but the result won't have any straightforward relationship to the value of the float. The second low-order bit of an int, for example, has a specific meaning; it contributes 0 to the value if it's 0, and 2 to the value if it's 1; the corresponding bit of a float has a meaning, but it's quite different (it contributes a value that depends on the value of the exponent).

The situation with pointers is quite similar. A pointer value has a meaning: it's the address of some object (or any of several other things, but we'll set that aside for now). On most current systems, interpreting the bits of a pointer object as if it were an integer gives you something that makes sense on the machine level. But the language itself does not guarantee, or even hint, that that's the case.

Pointers are not numbers.

A concrete example: some years ago, I ran across some code that tried to compute the difference in bytes between two addresses by casting to integers. It was something like this:

unsigned char *p0;
unsigned char *p1;
long difference = (unsigned long)p1 - (unsigned long)p0;

If you assume that pointers are just numbers, representing addresses in a linear monolithic address space, then this code makes sense. But that assumption is not supported by the language. And in fact, there was a system on which that code was intended to run (the Cray T90) on which it simply would not have worked. The T90 had 64-bit pointers pointing to 64-bit words. Byte pointers were synthesized in software by storing an offset in the 3 high-order bits of a pointer object. Subtracting two pointers in the above manner, if they both had 0 offsets, would give you the number of words, not bytes, between the addresses. And if they had non-0 offsets, it would give you meaningless garbage. (Conversion from a pointer to an integer would just copy the bits; it could have done the work to give you a meaningful byte index, but it didn't.)

The solution was simple: drop the casts and use pointer arithmetic:

long difference = p1 - p0;

Other addressing schemes are possible. For example, an address might consist of a descriptor that (perhaps indirectly) references a block of memory, plus an offset within that block.

You can assume that addresses are just numbers, that the address space is linear and monolithic, that all pointers are the same size and have the same representation, that a pointer can be safely converted to int, or to long, and back again without loss of information. And the code you write based on those assumptions will probably work on most current systems. But it's entirely possible that some future systems will again use a different memory model, and your code will break.

If you avoid making any assumptions beyond what the language actually guarantees, your code will be far more future-proof. And even leaving portability issues aside, it will probably be cleaner.

温柔戏命师 2024-12-10 01:08:46

这里存在如此多的疯狂...

如果您只想打印出指针的表示形式,那么 %p 通常是正确的格式说明符。永远、永远不要使用%d

int 的长度和指针的长度(void* 或其他)没有关系。 i386 上的大多数数据模型恰好具有 32 位 int 和 32 位指针 - 其他平台(包括 x86-64)并不相同! (这在历史上也被称为“全世界都是 VAX 综合症”。) http: //en.wikipedia.org/wiki/64-bit#64-bit_data_models

如果出于某种原因您想在整型变量中保存内存地址,请使用正确的类型! intptr_tuintptr_t。它们位于 stdint.h 中。请参阅http://en.wikipedia.org/wiki/Stdint.h#Integers_wide_enough_to_hold_pointers

So much insanity present here...

%p is generally the correct format specifier to use if you just want to print out a representation of the pointer. Never, ever use %d.

The length of an int and the length of a pointer (void* or otherwise) have no relationship. Most data models on i386 just happen to have 32-bit ints AND 32-bit pointers -- other platforms, including x86-64, are not the same! (This is also historically known as "all the world's a VAX syndrome".) http://en.wikipedia.org/wiki/64-bit#64-bit_data_models

If for some reason you want to hold a memory address in an integral variable, use the right types! intptr_t and uintptr_t. They're in stdint.h. See http://en.wikipedia.org/wiki/Stdint.h#Integers_wide_enough_to_hold_pointers

预谋 2024-12-10 01:08:46

尽管绝大多数 C 实现都使用相同的表示形式存储指向各种对象的指针,但 C 标准并不要求所有实现都这样做,甚至也没有提供任何方法使程序能够利用表示形式的通用性测试实现是否遵循常见做法,如果实现不遵循则拒绝运行。

如果在某个特定平台上,int* 保存一个字地址,而 char*void* 将字地址与一个字组合起来,标识单词中的字节,将 int* 传递给期望检索 char*void* 类型的可变参数的函数将导致该函数尝试从堆栈中获取更多数据(字地址加上补充字)比已推送(只是字地址)。这可能会导致系统以不可预测的方式出现故障。

许多针对所有指针使用相同表示的常见平台的编译器将处理传递非 void 指针的操作,其方式与处理在传递之前将指针强制转换为 void* 的操作完全相同。它。因此,他们没有理由关心作为可变参数传递的指针类型是否与接收者期望的指针类型精确匹配。尽管标准可以指定此类没有理由关心指针类型的实现应该表现得就像指针被强制转换为 void* 一样,但 C89 标准的作者避免描述任何不关心指针类型的实现。对于所有符合标准的编译器都是通用的。该标准的术语是“未定义的行为”,该结构表示 99% 的实现应该以相同的方式处理,但 1% 的实现可能会不可预测地处理。实现可以而且通常应该通过指定如何处理此类构造来扩展语言的语义,但这是标准管辖范围之外的实现质量问题。

Although it the vast majority of C implementations store pointers to all kinds of objects using the same representation, the C Standard does not require that all implementations do so, nor does it even provide any means by which a program which would exploit commonality of representations could test whether an implementation follows the common practice and refuse to run if an implementation doesn't.

If on some particular platform, an int* held a word address, while both char* and void* combine a word address with a word that identifies a byte within a word, passing an int* to a function that is expecting to retrieve a variadic argument of type char* or void* would result in that function trying to fetch more data from the stack (a word address plus the supplemental word) than had been pushed (just the word address). This could cause the system to malfunction in unpredictable ways.

Many compilers for commonplace platforms that use the same representation for all pointers will process an action which passes a non-void pointer precisely the same way as they would process an action which casts the pointer to void* before passing it. They thus have no reason to care about whether the pointer type that is passed as a variadic argument will precisely match the pointer type expected by the recipient. Although the Standard could have specified that such implementations which would have no reason to care about pointer types should behave as though the pointers were cast to void*, the authors of C89 Standard avoided describing anything which wouldn't be common to all conforming compilers. The Standard's terminology for a construct that 99% of implementations should process identically, but 1% would might process unpredictably, is "Undefined Behavior". Implementations may, and often should, extend the semantics of the language by specifying how they will treat such constructs, but that's a Quality of Implementation issue outside the Standard's jurisdiction.

烦人精 2024-12-10 01:08:46

在 C 中,void * 是一个无类型指针。 void 并不意味着 void...它意味着任何东西。因此,转换为 void * 与转换为另一种语言中的“指针”相同。

使用 (int *)&a 也应该可以...但是说 (void *) 的风格点是说——我不关心类型——只是它是一个指针。

注意:C 的实现可能会导致此构造失败,但仍然满足标准的要求。我不知道有任何这样的实现,但这是可能的。

In C void * is an un-typed pointer. void does not mean void... it means anything. Thus casting to void * would be the same as casting to "pointer" in another language.

Using (int *)&a should work too... but the stylistic point of saying (void *) is to say -- I don't care about the type -- just that it is a pointer.

Note: It is possible for an implementation of C to cause this construct to fail and still meet the requirements of the standards. I don't know of any such implementations, but it is possible.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文