Void pointer = int pointer = float pointer

I have a void pointer pointing to a memory address. Then i do

  • int pointer = void pointer

  • float pointer = void pointer

and then, look for them, get the values.

 { int x = 25; void *p = &x; int *pi = p; float *pf = p; double *pd = p; printf("x: n%d\n", x); printf("*p: %d\n", *(int *)p); printf("*pi: %d\n", *pi); printf("*pf: %f\n", *pf); printf("*pd: %f\n", *pd); return 0; } 

The dereferencing output of pi ( int pointer) is 25. However, the dereferencing output of pf ( float pointer) is 0,000. Also dereferncing pd ( double pointer) outputs a negative fraction, which keeps changing?

Why is this related to content (my processor is a little noun)?

+5
source share
7 answers

According to the C standard, you can convert any pointer to void * and convert it back, it will have the same effect.

To quote C11 , chapter §6.3.2.3

[...] A pointer to any type of object can be converted to a pointer to void and vice versa; The result should compare with the original pointer.

That's why when you overlay a void pointer on an int * , de-link and print the result, it prints correctly.

However, the standard does not guarantee that you can dereference this pointer to another data type. Essentially, it invokes undefined behavior.

So, dereferencing pf or pd to get a float or double is undefined behavior , since you read the memory allocated for an int like float or double . There's a clear case of mismtach that leads to UB.

To clarify, int and float (and double ) have different internal representations, so trying to overlay a pointer to another type, and then trying to dereference to get a value of another type will not work.

Associated, C11 , chapter §6.5.3.3

[...] If the operand is of type '' a pointer to a type, the result is of type ''. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

and for the invalid part of the value (my selection)

Among the invalid values ​​for dereferencing a pointer by the unary operator * is a null pointer, an address that is incorrectly selected for the type of object that it points to , and the address of the object after the end of its service life.

+6
source

In addition to the answers earlier, I think that what you expected could not be done due to the way floating point numbers are represented.

Integers are usually stored in two additional ways , basically this means that the number is stored as one part. Floats on the other hand are stored in a different way using the sign, base and exponent. Read here .

Thus, the basic idea of ​​conversion is not possible, since you are trying to accept a number represented as the original bits (for positive), and look at it as if it had been encoded differently, it will lead to unexpected results, even if the conversion was legal .

+4
source

So ... that's probably what is happening.

However, pf dereference output (float pointer) is 0.000

This is not 0. It is just very small.

You have 4 byte integers. Your integer looks like this in memory ...

 5 0 0 0 00000101 00000000 00000000 00000000 

What is interpreted as a float looks like ...

 sign exponent fraction 0 00001010 0000000 00000000 00000000 + 2**-117 * 1.0 

So you get the float out, but it's incredibly tiny. This is 2 ^ -117, which is almost indistinguishable from 0.

If you try to print a float with printf("*pf: %e\n", *pf); then he should give you something meaningful, but small. 7.006492e-45

Also dereferncing pd (double pointer) outputs a negative fraction that keeps changing?

Doubles 8 bytes, but you only define 4 bytes. A change in the negative fraction is the result of a search for uninitialized memory. The value of uninitialized memory is arbitrary and normal to see how this changes with each run.

+3
source

There are two types of UB here:

1) Strict anti-aliasing

What is a strict alias rule?

"Strict anti-aliasing is the assumption made by the C (or C ++) compiler that pointers to recognizing objects of different types will never refer to the same memory location (that is, aliases of each other.)"

However, strict overlay can be disabled as a compiler extension, such as -fno-strict-aliasing in GCC. In this case, your version of pf will work well, although the implementation is defined, assuming nothing else went wrong (usually float and int are 32-bit types and 32-bit aligned on most computers, usually ). If your computer uses IEEE754 single , you can get a very small floating point denorm , which explains the result you are seeing.

Strict anti-aliasing is a controversial feature of recent versions of C (and is considered a mistake by many people) and makes it very difficult and more hacky than before doing aka type punning in C.

Before you are knowledgeable about how to write and how it works with your version of the compiler and hardware, you should avoid this.

2) Memory from the bound

The pointer points to an amount of memory of size int , but you look for it as a double , which is usually twice the size of int , you basically read half of the double garbage from somewhere in the computer, so your double continues to change.

+2
source

The types int , float and double have different layouts of memory, representation and interpretation.

On my machine, int is 4 bytes, float is 4 bytes, and double is 8 bytes.

This is how you explain the results you see.

Canceling an int pointer works, obviously, because the original data was int .

By removing the float pointer, the compiler generates code to interpret the contents of 4 bytes in memory as a float . A value of 4 bytes, if interpreted as a float, gives you 0.00. See how the float displayed in memory.

By undoing the double pointer, the compiler generates code to interpret the contents in memory as double . Since double larger than int , it accesses 4 bytes of the original int and an extra 4 bytes on the stack. Since the content of these extra 4 bytes depends on the state of the stack and is unpredictable from start to start, you see variable values ​​that correspond to the interpretation of only 8 bytes as double .

+1
source

Further,

 printf("x: n%d\n", x); //OK printf("*p: %d\n", *(int *)p); //OK printf("*pi: %d\n", *pi); //OK printf("*pf: %f\n", *pf); // UB printf("*pd: %f\n", *pd); // UB 

Access in the first 3 printfs is great when you access int through an lvalue type of type int . But the following 2 do not correspond to the penalty 6.5, 7, expressions.

An int * not a compatible type with float * or double * . Thus, calls in the last two calls to printf () cause undefined behavior.

C11, $ 6.5, 7 states:

The object must have a stored value, accessible only with the value of the lvalue expression, which has one of the following types:
- a type compatible with the effective type of the object,

- qualified version of the type compatible with the effective type of the object,

- a type that is a signed or unsigned type corresponding to the effective type of the object,

- a type that is a signed or unsigned type corresponding to a qualified version of an effective object type,

- an aggregate or unified type that includes one of the above types among its members (including recursively, a member of a sub-aggregate or contained union) or

- type of symbol.

+1
source

The term "C" is used to describe two languages: one, invented by K & R, in which pointers identify places of physical memory, and one that is obtained from one that works the same way when pointers are read and written in ways that comply certain rules, but can behave arbitrarily if they are used in other ways. Although the latter language is defined by standards, the former language became popular for microcomputer programming in the 1980s.

One of the main obstacles to creating efficient machine code from C code is that compilers cannot tell which pointers aliases can use for variables. Thus, any time code refers to a pointer that can point to a given variable, the generated code is necessary to ensure that the contents of the memory identified by the pointer and the contents of the variable match. It can be very expensive. The people writing the C89 standard decided that compilers should be allowed to assume that named variables (static and automatic) will be available only with pointers of their type or character types; people writing C99 decided to add additional restrictions to their dedicated storage.

Some compilers offer a means by which code can guarantee that access using different types will go through memory (or at least behave as if they did), but unfortunately I don’t think that there is some standard for this. C14 added a memory model for use with multi-threaded processing, which should be able to provide the required semantics, but I do not think that compilers should abide by such semantics in cases where they can say that there is no access to anything for external threads [even if the transition through memory will be necessary to achieve the correct single-threaded semantics].

If you use gcc and want to have memory semantics that work like K & R, use the -fno-strict-aliasing command-line option. To make the code efficient, it will be essential to use the “limiting” qualifier that was added to C99. Although gcc authors seem to have focused more on type-based alias rules than "restrict", the latter should allow more useful optimizations.

0
source

Source: https://habr.com/ru/post/1238858/


All Articles