1

I know that people have already answered what volatile is and that (int *) is just casting but I cannot understand what really goes under the hood in this expression: volatile int *p = (int *)0x0

So we have a pointer p to an int that can obviously have its value changed unexpectedly. We assign it another pointer that points to the memory address 0? So is it a pointer to a pointer or am I making it more complex than it should be? I would really appreciate it if you could provide a simple sketch like this as it helps me understand.

Sketch

Ari
  • 324
  • 3
  • 13
  • 3
    It is just initializing a volatile integer pointer to NULL. The address 0 (NULL) is a special address/value used to indicate that the pointer doesn't point to anything so it should not be used (dereferenced). – Marker Sep 05 '18 at 15:10
  • It's an *assignment* of one pointer -- the right hand side `(int *)0x0` to another pointer the left hand side `p` with a volatile type qualifier. So, no, `p` is not a pointer to a pointer. – MFisherKDX Sep 05 '18 at 15:14
  • 2
    To make this worse, there's actually many systems where address 0 is a valid register. Long time ago I had some bug where a null pointer access bug made a microcontroller GPIO go haywire. Sometimes C simply decides to shoot you in the foot. – Lundin Sep 05 '18 at 15:32
  • @Marker Strictly speaking, it's an incorrect way of initializing `p` to [a null pointer constant.](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3) The cast to `int *` does not result in a standard null pointer constant - only an integer zero value or an integer zero value cast to `void *` are null pointer constants by the C standard. Note also that `NULL`, while also a null pointer constant, does not have to have a zero value, as Lundin mentions above. IMO the late addition to the C standard of zero values as null pointer constants is a sop to poor code. – Andrew Henle Sep 05 '18 at 15:52
  • @AndrewHenle, I'm curious, in which of the following statements does p not ultimately get set to the same value? int* p = (int*)0; int *p = (void *)0; int *p = 0; – Marker Sep 06 '18 at 01:22
  • @Marker How do you know `int *` and `void *` have the same internal representation? [They don't have to.](https://port70.net/~nsz/c/c11/n1570.html#6.2.5p28) Did you know that [you can't even cast a function pointer to `void *`](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p1) in strictly-conforming C code? – Andrew Henle Sep 06 '18 at 09:14
  • @AndrewHenle, as I said, I'm just curious. On what platforms (in C/C++) does the pointer ultimately not get assigned the to same value? Now you've also made me curious, on what C/C++ platforms do "int *" and "void *" not have the same internal representation? I see from your links what the spec says, but on what platforms does this this actually happen? – Marker Sep 06 '18 at 11:50
  • @Marker [Quite a few in fact:](http://c-faq.com/null/machexamp.html) "Older, word-addressed Prime machines were also notorious for requiring larger byte pointers (char *'s) than word pointers (int *'s)... Some 64-bit Cray machines represent int * in the lower 48 bits of a word; char * additionally uses some of the upper 16 bits to indicate a byte address within a word." Those are a couple of cases where a cast to `( int * )` can produce a different result than a cast to `void *`, which by the C standard has the same representation as `char *`. Thus `(int *)0` is not a "null pointer constant". – Andrew Henle Sep 06 '18 at 15:49
  • @AndrewHenle, interesting, thanks. Sounds like mostly older and/or more obscure (certainly not mainstream) machines so maybe only a few percent though still true. I have worked on a number of different platforms over the years (Motorola, Intel, Microchip, ARM) and never encountered that. BTW, I never said (int *)0 was a "null pointer constant"; for readability and maintainability, I have always used NULL which should be (I hope) defined appropriately the current compiler/platform. – Marker Sep 06 '18 at 17:44

2 Answers2

2

volatile NULL pointer does not have too much sense as no one is going to dereference it.

But if I make this expression to have more sense ( this is STM32 microcontroller specific example)

 volatile uint32_t *GPIOA_CRL = (volatile uint32_t *)0x40010800UL;

I declare the volatile pointer GPIO_CLR to the uint32_t object. The assignment casts the address of this hardware register defined by the unsigned long constant to the pointer type.

0x40010800UL - address of the hardware register

(volatile uint32_t *) - converts the unsigned long address to the pointer

Then I can set or read the value of this register.

Why have I defined this pointer as volatile? Because it can be changed by the hardware and compiler will not know about it. So volatile will force the compiler to read the value stores under this address before every use (when i dereference this pointer) and store it after every change. Otherwise the compiler may optimize those reads and writes as it will see no effect of it in the normal program execution path.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Please remove first para. I don't see where there is enough information in the question for you to make the intuitive leap that having a pointer that points to address zero makes sense or not for this particular question. It depends on the compiler and tool chain and the target hardware. https://stackoverflow.com/questions/2761360/could-i-ever-want-to-access-the-address-zero and https://stackoverflow.com/questions/2759845/why-is-address-zero-used-for-the-null-pointer – Richard Chambers Sep 05 '18 at 19:29
  • @RichardChambers the standard defines NULL pointer this way 6.3.2.3. Making the NULL pointer volatile does not make any sense. If the system have a valid address which can be accessed by cast of zero to the pointer - it is an implementation exception from the rule `.I fa n invalid v alue has been assigned to the pointer ,the beha vior of the unary * operator is undefined` and – 0___________ Sep 05 '18 at 19:45
  • `Among the in valid v alues for dereferencing a pointer by the unary * operator are a **null pointer** ,a n address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.` – 0___________ Sep 05 '18 at 19:45
  • @P__J__: There exist platforms which map hardware registers starting at address zero. A quality implementation intended for low-level programming on such an implementation will process a request to write `*((uint16_t *volatile)0)` as a request to perform a 16-bit write to address zero, with whatever effect that happens to have. – supercat Sep 05 '18 at 23:32
  • @supercat . As I wrote it is an implementation exception. – 0___________ Sep 05 '18 at 23:36
  • @P__J__: It's not really an exception from the rule. The rule says that implementations can do anything they want. Quality implementations suitable for low-level programming will want to process it like any other `volatile` (the `volatile` is important because quality implementations, even for platforms where address zero is meaningful, may squawk at attempts to write to that address with an lvalue that isn't qualified `volatile`). Processing behavior usefully is a subcategory of "do whatever they want", not an exception to it. – supercat Sep 05 '18 at 23:43
  • @supercat please. NULL pointer as standard says is not dereferenvable. If zero address is valid the implementation should define another not referencable NULL pointer. Something which cannot be referenced - by definition is not side effects prone. So the volatile NULL pointer does not make any sense but syntactically is valid of course. – 0___________ Sep 05 '18 at 23:56
  • @P__J__: All the Standard says about violation of runtime constraints is that it does not specify any requirements or guarantees about what implementations do in response. As with all forms of UB, implementations are free to offer whatever guarantees they see fit in cases where the Standard imposes no requirements. – supercat Sep 06 '18 at 03:46
1

Let`s analyze this line.

volatile int * p; declares a pointer to a volatile int. A volatile int is from the storage and semantics a normal int. But volatile instructs the compiler, that its value could change anytime or has other side effects.

On the right side of the assignment you have (int *) 0x0. Here you tell the compiler, that it should assume a pointer to int which points to the address 0x0.

The full assignment volatile int * p = (int *) 0x0; assigns p the value 0x0. So in total you told the compiler, that at address 0x0 is an integer value which has side effects. It can be access by *p.

volatile

You seem to be unclear what volatile means. Take a look at this code:

int * ptr = (int *) 0x0BADC0DE;
*ptr = 0;
*ptr = 1;

An optimizing compiler would take a short look on this code and say: Setting *ptr to 0 has no effect. So we will skip this line and will only assign 1 immediately to safe some execution time and shrink the binary at the same time.

So effectively the compiler would only compile the following code:

int * ptr = (int *) 0x0BADC0DE;
*ptr = 1;

Normally this is OK, but when we are talking about memory mapped IOs, things get different.

A memory mapped IO is some hardware that can be manipulated by accessing special RAM addresses. A very simple example would be an output -- a simple wire coming out of your processor.

So let's assume, that at the address 0x0BADC0DE is a memory mapped output. And when we write 1 to it, this output is set to high and when we write 0 the output is set to low.

When the compiler skips the first assignment, the output is not changed. But we want to signalize another component something with a rising edge. In this case we want to get rid of the optimization. And one common way to do this, is to use the volatile keyword. It instructs the compiler that it can't check every aspect of read or write accesses to this register.

volatile int * ptr = (int *) 0x0BADC0DE;
*ptr = 0;
*ptr = 1;

Now the compiler will not optimize the accesses to *ptr and we are able to signalize a rising edge to an outside component.

Memory mapped IOs are important in programming embedded systems, operating systems and drivers. Another use case for the volatile keyword would be shared variables in parallel computing (that in addition to the volatile keyword would need some kind of mutex of semaphore to restrict simultaneous access to these variables).

HeLLo
  • 259
  • 1
  • 6