weird difference between int pointer and float pointer
please see my codes below
#include <stdio.h>
#include <stddef.h>
typedef struct _node
{
int a;
char *s;
}Node, *nodePtr;
int main(int argc, char *argv[])
{
char *str = "string"; /*str points to satic storage area*/
Node nd;
nodePtr pNode = NULL;
size_t offset_of_s = offsetof(Node,s);
nd.a = 1;
nd.s = str;
pNode = &nd;
/*Get addr of s, cast it to a different data types pointer, then de-reference it*/
/*this works, print "string"*/
printf("%s\n", *(int*)((char*)pNode + offset_of_s));
/*this sucks, print (null)*/
printf("%s\n", *(float*)((char*)pNode + offset_of_s));
return 0;
}
i attempt to get the address of the s member of the Node structure, cast it to a data types not less than 4 bytes(4 byte is the width of a point开发者_开发百科er on my machine), then de-reference the pointer as a argument to printf.
i do think the outcome of two printfs should be the same, but the second one displays "(null)" .
float and int have the same byte width on my machine, is the internal different representation of the two types that cause this ?
thanks in advance !
Your program invokes undefined behavior because the types of the arguments to printf()
are not what printf expects. There is no way to predict the outcome by looking at the source code.
C99-TC3, §7.19.6.1/9
If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.
However, if you are interested in the reason the behavior you've observed is as it is, it's likely that your compiler is one of those that pass floating-point values to printf() in the floating-point CPU registers. (GNU and CLang do that, for example). The second call to printf placed the dereferenced value in a floating-point register, but printf
, seeing the %s
conversion specifier, looked at the register where a char*
would have been passed, likely a general-purpose register, which happened to be zero in your case.
PS: Here's what GCC 4.6.1 makes out of it on my linux
main:
pushq %rbx
leal .LC0(%rip), %ebx
movl $.LC1, %esi
subq $16, %rsp
movl %ebx, %edx
movl $1, %edi
movq $.LC0, 8(%rsp)
xorl %eax, %eax
call __printf_chk
movd %ebx, %xmm0
movl $.LC1, %esi
movl $1, %edi
movl $1, %eax
unpcklps %xmm0, %xmm0
cvtps2pd %xmm0, %xmm0 # this is where your value went
call __printf_chk # is NOT gonna read from xmm0!
addq $16, %rsp
xorl %eax, %eax
popq %rbx
ret
Same story with clang 2.9
...
movl $.L.str, %ebx
xorb %al, %al
movl $.L.str1, %edi # .L.str1 is your format "%s\n"
movl $.L.str, %esi # .L.str is your static "string"
callq printf
movd %ebx, %xmm0 # your value is in xmm0 again
cvtss2sd %xmm0, %xmm0 # promoted to double, but still in xmm0
movb $1, %al
movl $.L.str1, %edi
callq printf # printf has no idea
Your expectations are apparently based on your belief that variadic arguments of variadic functions are passed to those functions in some specific way. This is already very implementation-dependent, so from the point of the formal C language your experiments already make very little sense.
I'd guess that you expect the variadic arguments to be copied to the "variadic argument array" of some sort (stack frame?) as blocks of raw memory, regardless of their type-specific semantics. For this reason you apparently believe that an int
argument should be passed in exactly the same way as a float
argument since both types happen to have the same size on your platform.
This assumption is totally unfounded and incorrect. What is actually passed to printf
in this case is the values of the arguments in question, and since these values have completely different type-specific semantics, they can easily be passed in completely different ways. Needless to say, the behavior of your code is undefined for more reasons than one.
One basic thing that you need to understand in this case is that it is completly impossible to pass a float
value as a variadic parameter of a variadic function. All float
values are automatically promoted to double
values before passing, as required by the language specification. (The same applies to char
and short
values, which are always promoted to int
first.) Considering that in your case the float
value was obtained by reinterpreting memory occupied by a pointer object, and then promoted to double
, it is not surprising that the results you observe make no sense whatsoever.
Another basic thing that you need to understand that reinterpreting memory occupied by an object of one type and an object of another type is no allowed by C language (in a sense that the resultant behavior is undefined). You are not allowed to reinterpret memory occupied by a pointer object as an int
object. And this is exactly what you are attempting to do. Even the first of your printf
s, which allegedly "works as expected", does so only by accident.
Yes. The internal representation of a float and an integer in binary is vastly different.
If you want the address, use the "%p" format specifier with printf(). It's been in C since K&R2 and maybe before.
精彩评论