0

<what_I_have_tried>

  1. Looking at the suggestion list.
  2. In Google: how not to use pointers c (idea: is it an idea to summarize all ways pointer declaration, assignment and use could go wrong in a SO question? I know it would be helpful for me, since I'm trying to understand pointers by searching edge cases in order to see if I understand all the concepts)
  3. In Google: errors from pointers c
  4. In SO: int to int*
  5. Read a bunch of general stuff on pointers. I view * as a dereference operator. I used to view * as a sort of 'second variable' and saw a pointer as a 'macro variable' (e.g. *a and b are parts of a 'macro variable'). This is completely wrong. Now I view int *a as one variable (just like int b), which is accessed by typing a and * is an operator. Interestingly, one small idea I have then is that you cannot dereference a pointer at the moment you declare it, since the * is then part of the type declaration (e.g. int *p is a pointer declaration. Likewise, int* *q -- confusing syntax is on purpose -- is a pointer to pointer declaration, i.e. you cannot use the dereference operator since the * is parsed as part of the type).

</what_I_have_tried>

Here is the question. Note, I'm using edge cases to understand a concept, not to promote bad software practices. I'm testing to see to what point I understand dereferencing/referencing and pointers in general, hence I try to let C do things it probably doesn't want to do normally. Considering the following program. It's a pure beauty ;)

#include "stdio.h"

int main(void) {
    int a = 0;
    int b = 5;
    printf("a: %d, b: %d\n", a, b);
    a = &b; //bad intentions! Lets see what happens, we can cast right?
    printf("a: %d, b: %d, &b: %d\n", a, b, &b);
    printf("*a: %d", (*((int *)a)));

    return 0;
}

The output is:

a: 0, b: 5
a: 1508416372, b: 5, &b: 1508416372
Segmentation fault: 11

My thoughts for the first 30 minutes. "How can this happen?! They have the SAME MEMORY ADDRESS!!" Excuse my screaming, but this looks rather frustrating. Before going to SO, I opened up GDB breaking at the final printf. Compilation settings are: gcc -g -O0 test.c -o a. The relevant GDB output is:

(gdb) p/x a
$3 = 0x5fbffb34
(gdb) p/x &b
$4 = 0x7fff5fbffb34

Wait what? The function printf is not showing me the full output. I did not expect this.

So... to test the concept again I added 0x7fff to a. It didn't work, because adding 0x7fff to 0x5fbffb34 is not the same as appending it.

Now that I think about it, I think I have the answer. I'm running a 64 bit machine and I've been assuming that int * and int get the same amount of bytes allocated but it could be that it doesn't work because int * gets 4 bytes (32 bits) or 8 bytes (64 bits) allocated -- I'm running 64 bits on Mac OSX. Is this why it doesn't work?

I'm stopping here, else I'm breaking the Q&A format I suppose. I was writing this question while debugging, so the chronology is quite preserved. I'm now going to see if I can append 0x7fff somehow since I don't have a 32-bit machine to test on.

Edit and conclusion (warning, it might be too in-depth for the earliest beginners):

With the answer in mind, I can conclude the following. In GCC pointers store memory addresses. From a non-compiler but practical perspective pointers store references to objects and functions (and in GCC that gets translated to a memory address) -- as a programmer this is as far as I need to go. I read from this question that the C standard views this even a bit more broadly.

I can also conclude that, conceptually, you can dereference whatever you want. Dereferencing, from a conceptual perspective, always works or is at the very least attempted to be executed. In the code of my question 0x5fbffb34 was attempted to be dereferenced or was even dereferenced. However, a conceptual perspective is not a practical one. In the real world, the operating system does some checks when the dereference is attempted or when the value is returned. Hence, it interfered and kills the program since the memory address was not in the boundaries of my program (that's my guess at least).

Another conclusion is that people who draw boxes with arrows when they declare and initialize a pointer are wrong. In my opinion, the arrow should only be drawn when a pointer is dereferenced. This is because the arrow is part of dereferencing a value from a memory address. A pointer (and its related type, e.g. int *) only helps and guides to get defined behaviour. It's kind of like when you dereference a value from a pointer variable (e.g. int b = 0; int *a = &b; int c = *a;), then you get a 'safe(ish) arrow'. When you dereference a value from a non-pointer variable, then you get a 'deadly(ish) arrow' as can be seen in my program.

Community
  • 1
  • 1
Melvin Roest
  • 1,392
  • 1
  • 15
  • 31
  • Please try this `printf("%zu vs %zu\n", sizeof(int), sizeof(void *));` does it print `4 vs 4` or `4 vs 8`? Or something eles? – Iharob Al Asimi Feb 13 '16 at 11:19
  • 4 vs 8... Thanks for testing the hypothesis. I am going to make this work. That int will become 8 bytes! – Melvin Roest Feb 13 '16 at 11:22
  • 1
    "I guess it's a bad thing to do, but why isn't it possible?" - Answer: Take the first halp of the sentence, remove "I guess" And you got the answer. – too honest for this site Feb 13 '16 at 11:24
  • 1
    When people explain pointers, these type of things should be explained as well. They don't unfortunately, they only explain the general concept of a pointer without looking too much at casting or types in general. So I have to make assumptions and I need to test them. There are many things in C that I don't see the value of or that I guess are bad, and later I found out they are good things. As a beginner, I cannot differentiate between when something is bad or when something turns out to be a good thing after all, or -- even worse -- when something is bad but it is in the language anyway. – Melvin Roest Feb 13 '16 at 11:30
  • @MelvinRoest You are 100% right, pointers are understood only by a few because when you try to learn about them it seems like no one explains any subtleties. You only master them with practice. – Iharob Al Asimi Feb 13 '16 at 11:31
  • @MelvinRoest: No it has not to. Because you are far away from the C standard in the land of implementation defined behaviour **at best**. or even undefined behaviour **at worst**. Already your idea about a pointer being some kind of "meta variable" is wrong. A pointer is just a variable like any other. It just holds an address. What you might think about are references. These are not supported in C. – too honest for this site Feb 13 '16 at 11:36
  • @Olaf you're right it is wrong. I realized this yesterday and I stated it here to show where I was coming from. I state further on in the question that I don't hold this view anymore. So while it is wrong, I have the idea you're assuming that I still held that believe. If this is true, then CTRL + F the sentence "This is completely wrong" and you can conclude for yourself that I got rid of this idea before asking the question. Yet, since I was coming from that place I now had to test to see what pointers really are. Hence I tested the extent of * being an operator. – Melvin Roest Feb 13 '16 at 11:45

1 Answers1

2

Don't use int for that, use uintptr_t from stdint.h. The problem might be that sizeof(int) < sizeof(void *) on your platform, and that leads to overflow in the assignment

a = &b;

my compiler gcc with warnings enabled told me:

cast to pointer from integer of different size

but with uintptr_t it worked as expected

#include <stdint.h>
#include <stdio.h>

int main(void) 
{
    uintptr_t a = 0;
    int b = 5;

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

    a = (uintptr_t) &b; //bad intentions! Lets see what happens, we can cast right?

    printf("a: %ld, b: %d, &b: %p\n", a, b, (void *) &b);
    printf("*a: %d", (*((int *) a)));

    return 0;
}

Although this is allowed, I have never found a use case for such conversion and this kind of code is very dangerous and unecessary. So if it's only with the purpose of testing what you learned it's ok but never do this.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • The only types guaranteed to hold a pointer are `(u)intptr_t`. `ptrdiff_t` is not. There well can be implementations where `ptrdiff_t` e.g. is 16 bits, while a pointer is 32 bits. 16 bit x86 with far-pointers comes into mind here. – too honest for this site Feb 13 '16 at 11:26
  • 1
    I think you forgot an edit. (Funny, I tend to forget about `ptrdiff_t`) – too honest for this site Feb 13 '16 at 11:28
  • Both answers worked actually even without casting &b. – Melvin Roest Feb 13 '16 at 11:28
  • @MelvinRoest Without casting `&b`? you mean to `void *`? it could work but the correct thing to do is cast. – Iharob Al Asimi Feb 13 '16 at 11:29
  • @Olaf, but it has to. After all it must hold a zero subtracted pointer as well. – StoryTeller - Unslander Monica Feb 13 '16 at 11:29
  • @StoryTeller It's not guaranteed, it's true read the standard. – Iharob Al Asimi Feb 13 '16 at 11:30
  • @MelvinRoest: That still is a terrible idea. Using C without using pointer types is quite impossible for any non-trivial program. And useless casting is is worse than bad design, as it even more weakens the already weak typing in C. Note that you already cannot use `printf` without using pointers. – too honest for this site Feb 13 '16 at 11:31
  • @iharob, The standard makes special mention of the case where the subtracted address is zero? – StoryTeller - Unslander Monica Feb 13 '16 at 11:31
  • @StoryTeller: What do you mean with "it has to"? What "has to"? And why do you mean with "zero-subtracted ..."? – too honest for this site Feb 13 '16 at 11:32
  • @StoryTeller I will read it today, but right now I have finished preparing coffee and have to go back to what I was doing. I promise to read it later today and come back and add the necessary qoute to explain this in a better way. – Iharob Al Asimi Feb 13 '16 at 11:33
  • There are multiple terrible ideas in my program but there's one good idea. I'm learning about pointers, and hopefully other people as well. If you see point 5 of what I have tried then you can see that I initially thought that a pointer was some kind of 'macro variable' (e.g. {*a : value, a : value} -- a nightmarish idea by the way). But everyone kept saying that * is an operator. This program is trying to explore to what extent this really is true. – Melvin Roest Feb 13 '16 at 11:36
  • @Olaf, I mean that if the subtracted address is 0x0, the result must still fit in `ptrdiff_t`. – StoryTeller - Unslander Monica Feb 13 '16 at 11:37
  • @StoryTeller: When subtraction two pointers, you get a result type of `ptrdiff_t` already. But subtracting two pointers is only defined iff they point into the same array (or one entry past it). Otherwise you invoke undefined behaviour. And subtracting a _null pointer (constant)_ (which `0` is) from a pointer is by definition of a _null pointer_ UB. Childish "but it has to, because I want to" does not change that fact. – too honest for this site Feb 13 '16 at 11:41
  • "So if it's only with the purpose of testing what you learned it's ok but never do this." I will confirm that I will never do this. It was purely for learning. For example, I learned how detached most explanations on pointers really are. Most explanations describe the theoretical concept. What is not described is: accessing memory that isn't reserved for the program, casting rules for pointers and how pointers work under the hood (e.g. allocation in bytes). This means that eventhough one understands pointers, it's still easy to create bugs because the context of C or C++ isn't explained. – Melvin Roest Feb 13 '16 at 11:49
  • @Olaf, up until the last sentance you comment was both insightful and informative. The only petulance came from you end, and the supposed "quote". – StoryTeller - Unslander Monica Feb 13 '16 at 16:20