A pointer variable is an object that can point to another object. Here int *ptr = NULL; declares ptr as a pointer object, that potentially points to an object of int.
The value initially stored into this pointer object is NULL (it is initialized to NULL, so ptr does not point to any object).
Now, ptr too resides in memory. It needs enough bytes to contain the address of the pointed-to object. So it too needs to have an address. Therefore
ptrevaluates to the address of object thatptrpoints to.&ptrevaluates to the location of theptrobject itself in memory*ptrevaluates to the value of the object thatptrpoints to, if it points to an object. If it does not point to an object, then the behaviour is undefined.
Also, %p needs a void * as the corresponding argument, therefore the proper way to print them is
printf("%p\n", (void *)ptr);
printf("%p\n", (void *)&ptr);
ptr holds the value NULL which is what you assigned.
&ptr is the address to the variable ptr which in your case is 0x7fff3415dc40
You're setting a pointer to 0 (NULL) and then adding 1 to it; then you're converting the result to an int and printing the result. The key piece of knowledge you need here is that when you increment (add 1 to) a pointer, you actually add the size of the pointed-to object -- an int pointer is advanced to point to the next int. Since int is (apparently) 4 bytes on your platform, p is incremented to point to an address 4 bytes past where it starts.
Size of the int in C is (typically) 4 bytes. So incrementing a pointer by one unit means incrementing it's value by sizeof(int).
Also, you aren't printing the value of which pointer is directing (as this would certainly crash your program) but the value of the pointer itself. You should definitely take a look at any pointers tutorial in C (or in general).
For the purpose of the macro:
It assumes that there is an object of type TYPE at address 0 and returns the address of the member which is effectively the offset of the member in the structure.
This answer explains why this is undefined behaviour. I think that this is the most important quote:
If
E1has the type “pointer to class X,” then the expressionE1->E2is converted to the equivalent form(*(E1)).E2;*(E1)will result in undefined behavior with a strict interpretation, and.E2converts it to an rvalue, making it undefined behavior for the weak interpretation.
which is the case here. Although others think that this is valid. It is important to note that this will produce the correct result on many compilers though.
#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
is very similar to a fairly common definition of the standard offsetof() macro, defined in <stddef.h> (in C) or <cstddef> (in C++).
0 is a null pointer constant. Casting it to TYPE * yields a null pointer of type TYPE *. Note that the language doesn't guarantee (or even imply) that a null pointer has the value 0, though it very commonly does.
So (TYPE *)0 is notionally the address of an object of type TYPE located at whatever address the null pointer points to, and ((TYPE *)0)->ELEMENT)) is the ELEMENT member of that object.
The & operator takes the address of this ELEMENT member, and the cast converts that address to type size_t.
Now if a null pointer happens to point to address 0, then the (nonexistent) object of type TYPE object starts at address 0, and the address of the ELEMENT member of that object is at an address that's offset by some number of bytes from address 0. Assuming that the implementation-defined conversion from TYPE * to size_t behaves in a straightforward manner (something else that's not guaranteed by the language), the result of the entire expression is going to be the offset of the ELEMENT member within an object of type TYPE.
All this depends on several undefined or unspecified behaviors. On most modern systems, the null pointer is implemented as a pointer to address 0, addresses (pointer values) are represented as if they were integers specifying the index of a particular byte within a monolithic addressing space, and converting from a pointer to an integer of the same size just reinterprets the bits. On a system with such characteristics, the OFFSETOF macro is likely to work, and the implementation may choose to use a similar definition for the standard offsetof macro. (Code that's part of the implementation may take advantage of implementation-defined or undefined behavior; it's not required to be portable.)
On systems that don't have these characteristics, this OFFSETOF macro may not work -- and the implementation must use some other method to implement offsetof. That's why offsetof is part of the standard library; it can't be implemented portably, but it can always be implemented in some way for any system. And some implementations use compiler magic, like gcc's __builtin_offsetof.
In practice, it doesn't make much sense to define your own OFFSETOF macro like this, since any conforming C or C++ implementation will provide a working offsetof macro in its standard library.
So I use C# and I often see many devs use null.
What and which kind of situation do you use this variable?
I am reading c# guide on programming book and I am on Clearing memory now and I haven't encountered null yet. Should I be worried?