2

I can't understand the output of this program . What I get of it is , that , first of all , the pointers p, q ,r ,s were pointing towards null .

Then , there has been a typecasting . But how the heck , did the output come as 1 4 4 8 . I might be very wrong in my thoughts . So , please correct me if I am wrong .

int main()
{

    int a, b, c, d; 
    char* p = (char*)0; 
    int *q = (int *)0; 
    float* r = (float*)0; 
    double* s = (double*)0; 

    a = (int)(p + 1); 
    b = (int)(q + 1);
    c = (int)(r + 1);
    d = (int)(s + 1);

    printf("%d %d %d %d\n", a, b, c, d); 

    _getch();
    return 0; 
}
Najmus Sakib
  • 33
  • 1
  • 6

4 Answers4

2

Pointer arithmetic, in this case adding an integer value to a pointer value, advances the pointer value in units of the type it points to. If you have a pointer to an 8-byte type, adding 1 to that pointer will advance the pointer by 8 bytes.

Pointer arithmetic is valid only if both the original pointer and the result of the addition point to elements of the same array object, or just past the end of it.

The way the C standard describes this is (N1570 6.5.6 paragraph 8):

When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression.
[...]
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

A pointer just past the end of an array is valid, but you can't dereference it. A single non-array object is treated as a 1-element array.

Your program has undefined behavior. You add 1 to a null pointer. Since the null pointer doesn't point to any object, pointer arithmetic on it is undefined.

But compilers aren't required to detect undefined behavior, and your program will probably treat a null pointer just like any valid pointer value, and perform arithmetic on it in the same way. So if the null pointer points to address 0 (this is not guaranteed, BTW, but it's very common), then adding 1 to it will probably give you a pointer to address N, where N is the size in bytes of the type it points to.

You then convert the resulting pointer to int (which is at best implementation-defined, will lose information if pointers are bigger than int, and may yield a trap representation) and you print the int value. The result, on most systems, will probably show you the sizes of char, int, float, and double, which are commonly 1, 4, 4, and 8 bytes, respectively.

Your program's behavior is undefined, but the way it actually behaves on your system is typical and unsurprising.

Here's a program that doesn't have undefined behavior that illustrates the same point:

#include <stdio.h>
int main(void) {

    char c;
    int i;
    float f;
    double d;

    char   *p = &c;
    int    *q = &i;
    float  *r = &f;
    double *s = &d;

    printf("char:   %p --> %p\n", (void*)p, (void*)(p + 1));
    printf("int:    %p --> %p\n", (void*)q, (void*)(q + 1));
    printf("float:  %p --> %p\n", (void*)r, (void*)(r + 1));
    printf("double: %p --> %p\n", (void*)s, (void*)(s + 1));

    return 0; 
}

and the output on my system:

char:   0x7fffa67dc84f --> 0x7fffa67dc850
int:    0x7fffa67dc850 --> 0x7fffa67dc854
float:  0x7fffa67dc854 --> 0x7fffa67dc858
double: 0x7fffa67dc858 --> 0x7fffa67dc860

The output is not as clear as your program's output, but if you examine the results closely you can see that adding 1 to a char* advances it by 1 byte, an int* or float* by 4 bytes, and a double* by 8 bytes. (Other than char, which by definition has a size of 1 bytes, these may vary on some systems.)

Note that the output of the "%p" format is implementation-defined, and may or may not reflect the kind of arithmetic relationship you might expect. I've worked on systems (Cray vector computers) where incrementing a char* pointer would actually update a byte offset stored in the high-order 3 bits of the 64-bit word. On such a system, the output of my program (and of yours) would be much more difficult to interpret unless you know the low-level details of how the machine and compiler work.

But for most purposes, you don't need to know those low-level details. What's important is that pointer arithmetic works as it's described in the C standard. Knowing how it's done on the bit level can be useful for debugging (that's pretty much what %p is for), but is not necessary to writing correct code.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • okay , Sir , this is what I got . Since all the pointers were pointing to NULL initially , so pointer arithmetic was undefined in that case , now our compiler don't actually make an issue of it . But when we add up 1, then it it advances the address by the number of bytes (depending on the type of the pointer variable ) . Now , converting that address to an int gives me typical size of chars , ints , floats because of the pointer types they were basically refering to . that is why , I get this sort of an output . I hope I have understood what you said . – Najmus Sakib Aug 08 '15 at 08:51
  • @NajmusSakib: Yes, that's essentially right. One small wording quibble: the pointers weren't "pointing to NULL", they *were* null pointers. A null pointer doesn't point to anything. – Keith Thompson Aug 08 '15 at 18:58
1

Adding 1 to a pointer advances the pointer to the next address appropriate for the pointer's type.

When the (null)pointers+1 are recast to int, you are effectively printing the size of each of the types being pointed to by the pointers.

printf("%d %d %d %d\n", sizeof(char), sizeof(int), sizeof(float), sizeof(double) );

does pretty much the same thing. If you want to increment each pointer by only 1 BYTE, you'll need to cast them to (char *) before incrementing them to let the compiler know

Search for information about pointer arithmetic to learn more.

Steve Valliere
  • 1,119
  • 7
  • 12
  • `%d` expects an `int` argument; `sizeof` yields a `size_t` result. Use `%zu` (or cast to `int` if you have an old implementation that doesn't support `%zu`). And you don't mention that the behavior is undefined. – Keith Thompson Aug 07 '15 at 18:49
0

You're typecasting the pointers to primitive datatypes rather type casting them to pointers themselves and then using * (indirection) operator to indirect to that variable value. For instance, (int)(p + 1); means p; a pointer to constant, is first incremented to next address inside memory (0x1), in this case. and than this 0x1 is typecasted to an int. This totally makes sense.

Fahad Siddiqui
  • 1,829
  • 1
  • 19
  • 41
-1

The output you get is related to the size of each of the relevant types. When you do pointer arithmetic as such, it increases the value of the pointer by the added value times the base type size. This occurs to facilitate proper array access.

Because the size of char, int, float, and double are 1, 4, 4, and 8 respectively on your machine, those are reflected when you add 1 to each of the associated pointers.

Edit:

Removed the alternate code which I thought did not exhibit undefined behavior, which in fact did.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • No problem. Feel free to [accept this answer](http://stackoverflow.com/help/accepted-answer) if you found it useful. – dbush Aug 07 '15 at 18:26
  • Pointer arithmetic such as `char* p = (char*)0; a = (p + 1);` _is_ undefined behavior. Claiming "this code which does not invoke UB:" is at best mis-leading. Pointer arithmetic is only valid on a legitimate object's address (and 1 object pass that). `NULL` is not a legitimate object's address. – chux - Reinstate Monica Aug 07 '15 at 18:38
  • @chux The arithmetic itself is fine. You only encounter UB if you attempt to dereference said pointer. – dbush Aug 07 '15 at 18:45
  • 3
    @dbush: You're mistaken. Pointer arithmetic on a null pointer has undefined behavior, whether it's dereferenced or not. [N1570](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) 6.5.6p8: "If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined." – Keith Thompson Aug 07 '15 at 18:47
  • @KeithThompson For the sake of this answer, it doesn't matter that the behaviour is undefined though. – fuz Aug 07 '15 at 18:56
  • 2
    @FUZxxl: That's debatable -- but it certainly does matter that the answer claims that the revised code (using `%p`) does not have UB, when in fact it does,. – Keith Thompson Aug 07 '15 at 19:03
  • 1
    Also, `%p` requires an argument of type `void*`. Other pointer types should be explictly converted: `printf("%p\n", (void*)ptr)` – Keith Thompson Aug 07 '15 at 19:07
  • I found this interesting as I've worked on targets where 0 is a valid address. Reading the spec 7.19p3 on NULL, it is an implementation defined constant, which does not have to be 0. And anyway, in @dbush 's code, NULL is not used, 0 is. In which case is it not just a pointer to address 0? In which case 6.5.6p7 and 6.5.6p8 would seem to suggest that the code is defined. I wonder if any one can expand on the above comments to offer more evidence for why the above is undefined? (Ignoring the void* cast in printf, on which I agree). – Andrew Aug 07 '15 at 19:45
  • @Andrew See http://stackoverflow.com/a/22104122/2410359 , http://stackoverflow.com/a/22103835/2410359 . As @Keith Thompson commented the spec 6.5.6p8. It is not a question if the pointer it `0`, `NULL`, or `((char*)rand())`. It is a question if the pointer points to a valid object, (or 1 beyond), otherwise pointer arithmetic is UB. If `(char*)0` is a valid pointer to a `char`, `p++` is OK. – chux - Reinstate Monica Aug 07 '15 at 21:54
  • @chux I don't believe that either of the stackoverflow links you reference are relevant, as I referenced a pointer to 0 is not a NULL pointer (see 7.19p3). Though I do believe I see your point now, the `((char *) rand())` is what convinced me; I was interpreting 6.5.6p7 to indicate that any pointer that was not a pointer to an array should be treated as if it were, when in fact it does need to point to a valid object of the pointer type. Thanks for clearing that up for me. – Andrew Aug 07 '15 at 22:13
  • 1
    The reason this is UB is just that a pointer calculated from an invalid pointer will be an invalid pointer itself. Of course that means *anything* could be added to this pointer and the implementation would still conform. In practice, this will of course not happen (except for `NULL`, the compiler can most often not even tell whether this pointer points to a valid object or not). So I consider this discussion academic. Just remove the claim this wasn't UB and it's ok ... –  Aug 07 '15 at 22:22
  • @Andrew: `0` is by definition a null pointer constant, so `int *ptr = 0;` will cause `ptr` to contain a null pointer value, identical to `int *ptr = NULL;`. This null pointer value may or may not have an all-bits-zero representation, and it may or may not (on the machine level) point to location zero. This: `int zero = 0; int *ptr = (int*)zero);` may or may not set `ptr` to be a null pointer; the special-case rule that makes `0` a null pointer constant does not apply to a non-constant expression with the value `0`. Commonly null pointers *are* all-bits-zero, but it's not required. – Keith Thompson Aug 08 '15 at 19:01
  • @KeithThompson Thanks. You are correct. For the record it is 6.3.2.3p3 of the spec that explains exactly why I'm wrong and you're right. – Andrew Aug 08 '15 at 19:58