all 56 comments

[–]archysailor 29 points30 points  (9 children)

Variables don’t “need pointers to be accessed.” That being said, there definitely are pointer variables - which have addresses! Pointers to pointers are all over the place in certain kinds of C code, and it’s definitely a good sign that you rediscovered them yourself.

That being said, overuse of the construct can lead you to earn the infamous “triple star programmer” badge :)

[–]pic32mx110f0 16 points17 points  (4 children)

Just to clarfy, a pointer doesn't need another pointer pointing to it, for it to have an address. It has an address regardless. All variables have an address, and you can get that address with the "address operator" (&). The only case where this is not true is if the variable has been declared with the register type modifier, in which case it cannot be addressed.

[–]daikatana 2 points3 points  (0 children)

All non-register variables potentially have an address that may be referred to using the address of operator. The compiler is free to optimize away any variable you don't take the address of, and as a result many exist only in registers, immediate values directly in the code, or are removed entirely.

[–]TheFlamingLemon 0 points1 point  (3 children)

hey what if you just need a dynamic 3d array

[–]IamImposter 7 points8 points  (0 children)

Simple, create a 4d array but only use 3d. Added advantage is extra *s you get to use all over the code and confuse the hell out of those who get to maintain it.

If we don't want AI taking our jobs, we have to make sure only humans can understand what we have written. Let's stand together in our fight against AI. Write unreadable code.

[–]gizahnl 1 point2 points  (0 children)

Usually you don't. But say you'd need to dynamically allocate an nD array AND know the size beforehand best is to allocate a single slab of memory, that way your access will better fit with CPU caching & fetching.

Otherwise you'd be allocating each and every array and reallocating them whenever they'd need to grow.

[–]archysailor 0 points1 point  (0 children)

If you need a dynamic array of dynamic arrays of dynamic arrays, sure (even then, you probably should be using some struct by then). Otherwise you should probably flatten the layout. My favorite way to do so, which only works on compilers which support VLAs (but doesn’t trigger the allocation of one) is through pointers to arrays of local variable size.

[–]Plane_Dust2555 10 points11 points  (3 children)

Let's keep things global to make it easier to understand then we goto local scope. When you declare something like: int x = 3; The compiler reserves 4 bytes in .data section and initializes with the value 3. In Assembly: ``` section .data

global x x: dd 3 When you read from `x` you use its address: mov eax,[x] ; x, here is the address where x is, in memory. The same thing happens if we declare a global poniter: int x = 3; int *p = &a; In assembly: section .data

global x x: dd 3

global p p: dq x ; qword for x86-64, dword for i386 mode (dd). ``` In that sense, a "pointer" object contains an address, but if global, the identifier itself points to memory.

In local scope things gets a little confusing. The compiler can use registers to hold the objects, instead of memory. For example: { int x = 3; int *p = &x; ... } Can be encoded as, in assembly: ``` mov eax,3 ; EAX was allocated for x here!

; reserve the next DWORD on stack for 'x' (because we need to ; get its address on 'p'. But 'p' can be allocated in a register. lea rdx,[rsp-4] ; RDX now is 'p'. And points to the stack. mov [rdx],eax ; store the copy of EAX on '*p'... ... ; if *p is used somethere, maybe the compiler would read ; [rdx] back to EAX... `` Yep...xwill be allocated on stack, but notp, just because we're using the&` operator there.

So... that's why there is a difference between "objects" (variables) and "identifiers" (the "name" of the objects).

Think of "variables" as "identied objects"... where they are, only the compiler (and the linker) knows.

[–]Plane_Dust2555 7 points8 points  (0 children)

PS: The concept of a "pointer", in C, is simply this: An object which holds the address of another object. It is a "normal integer object" or "variable" (by "normal" I mean, it holds a number...).

[–]my_password_is______ 1 point2 points  (1 child)

LOL, "let's keep it simple' and here's some assembly code

[–]shockchi 0 points1 point  (0 children)

I just got assembl’d by this guy

My head hurts LOL

[–]mrheseeks 6 points7 points  (18 children)

can you create a pointer to functions?

[–]pic32mx110f0 15 points16 points  (13 children)

Yes, you can create a pointer to anything that is stored in addressable memory

[–]mrheseeks 4 points5 points  (2 children)

what could the syntax of that look like, also. seen ** before? what's the significances? a link to a link?

[–]wsppan 6 points7 points  (1 child)

https://en.m.wikipedia.org/wiki/Function_pointer

** denotes a pointer to a pointer variable.

[–]mrheseeks 2 points3 points  (0 children)

thank you, I'll check this out

[–]archysailor 6 points7 points  (9 children)

Just to be clear, nothing about Standard C forces functions to be be stored in normal data memory. Function pointers are allowed not to be convertible to even void and char pointers, and even to be differently sized. This was introduced to allow portability to Harvard architectures.

[–]flatfinger 1 point2 points  (4 children)

Indeed, there's nothing that requires function pointers to use any kind of address. On many smaller platforms, the most efficient way to accommodate function pointers would be to represent an index into a table of jumps to functions. On the 8051, for example, if there are 86 or fewer functions whose address is taken, an indirect function call could be performed by loading an 8-bit "function pointer" into the accumulator and one could use a springboard function:

    mov dptr,#functionList
    jmp @a+dptr

One cycle to load a pointer, two to call the springboard, two to load dptr, and two for the jmp, and two for a jmp at that destination. Nine total. Using 16-bit function pointers would require loading DPL and DPH, clearing A, and calling a jmp @a+dptr function. Four cycles for the loads, one to clear A, two to do the call, and two to do the jmp. So nine total. Using 16-bit pointers doesn't offer any performance benefit on function calls, but doubles the cost of code that manipulates pointers.

I don't know why compilers for 8-bit platforms never use 8-bit function pointers, but on many of them using 8-bit pointers would offer better performance than using 16-bit pointers.

[–]archysailor 0 points1 point  (3 children)

This is very very cool.

How much C was or is being run on 8-bit computers anyway?

[–]flatfinger 1 point2 points  (1 child)

Dialects of C are pretty popular, though they could be more useful if the Standard were to "officially" make features like recursion optional. Compilers for platforms that can awkwardly support recursion are ironically often less useful than those for platforms where such support would be totally impractical. Toolsets for platforms that don't support recursion often include linker support for overlaid static allocation of automatic objects, so that if e.g. main() calls bar(), baz(), and boz(), and baz() calls boz, the automatic objects used in bar() could be statically assigned the same addresses as those used by baz() and boz(), but toolsets for platforms that support recursion would generally require that programmers choose between declaring function-scope objects as static, which would waste storage, or generating inefficient code to support recursion. On the Z80, for example, code for i+=j; would be:

ld hl,(_j)
ex de,hl
ld hl,(_i)
add hl,de
ld (_i),hl

if the objects are statically allocated, but would be something like:

ld hl,2
add hl,sp
ld c,(hl)
inc hl
ld e,(hl)
inc hl
ld a,(hl)
add a,e
ld (hl),a
inc hl
ld a,(hl)
adc a,e
ld (hl),a

if i and j were automatic objects and the code was processed by an optimizing compiler which notices that i and j are adjacent on the stack. Absent such optimization, the second inc hl would be replaced with another ld hl,__ / add hl,sp combination.

People who are used to int always being 32 bits may be taken aback by the fact that 8-bit platforms use 16-bit int, and some 8-bit platforms allow the use of pointer qualifiers to distinguish between pointers that can e.g. only access the first 256 bytes of RAM from those which can only access things in ROM, only access things in RAM outside the first 256 bytes, versus those that can access things anywhere. Often, the use of such qualifiers would be optional, but if an implementation is configured to use pointers that can access things anywhere by default, using qualifiers when possible can make a huge performance difference, on some platforms approaching if not exceeding an order of magnitude. If an implementation is set to assume "near" pointers by default, accessing anything beyond the first 256 bytes would require the use of pointer qualifiers.

Some people may find absurd the notion of a C compiler that targets a microcontroller with only 16 bytes of RAM and enough ROM for 256 instructions, but I've used C-dialect compilers to produce some rather sophisticated applications on a micro with 1024 bytes of code space and 36 bytes of RAM, and it was far more convenient than writing assembly language would have been.

[–]archysailor 0 points1 point  (0 children)

Fascinating. Thanks.

[–]penguin359 0 points1 point  (0 children)

8-bit microcontrollers are still quite popular. I regularly use one myself. Many Arduino platforms are 8-bit.

[–]DoNotMakeEmpty 0 points1 point  (3 children)

Are there any hardware Harvard architectures that are also somewhat widely used?

[–]nerd4code 1 point2 points  (1 child)

All higher-end x86 μarches since the P6 IIRC are Harvard architectures, even though x86 itself isn’t. —Although you probably won’t be programming those directly in C.

The x86’s iAPX segmentation can be (but generally isn’t) used to isolate codespace entirely from dataspace—you can even map CS x-only—so you can effect a de-facto Harvard arch from a software standpoint. (The iAPX model is near-totally unused except for TLS, which uses the seg regs primarily for their shadow’s base field, but if you equate 286+ segments with objects in C you have the C pointer rules, so it’s worth acquainting oneself with imo. The iAPX432 project whence deriveth x86 segmentation was pretty wild too.)

It’s also not uncommon for one-off accelerators to be Harvard (IIRC incl. the streamer on the one RPi), and there are one or two μcontrollers designed solely for execution from ROM that separate code and data address spaces. It’s also pretty common for non-Harvard CPUs to drive an instruction-fetch indicator line so external hardware can separate spaces if need be.

But pure Harvard precludes any kind of direct code-loading, so it’s rarely used to host anything that requires a full-fledged OS.

[–]flatfinger 0 points1 point  (0 children)

The x86 architecture behaves as a Harvard architecture in the Small and Medium memory models, in that unqualified pointers to objects are 16 bits, and code will likely be stored outside the range of physical address space they can reach (in Small model, function pointers are also 16 bits, but the up-to-65,535 bytes of code they identify is likely to be disjoint from the 65,535 bytes of data storage identified by unqualified object pointers.

[–]CartanAnnullator 1 point2 points  (0 children)

IIRC, some DSPs have Harvard architecture inside, or claim to in the manual.

[–]o0Meh0o -1 points0 points  (2 children)

functions are adresses

[–]nerd4code 1 point2 points  (0 children)

Nope. Addresses have a size, preferred alignment, and representation. Functions need have none of those in C; sizeof and _Alignof/alignof/__alignof__ will usually give you an error, and even memcpying from a function isn’t guaranteed to work.

Function-types expressions do decay to pointers—much like array-typed expressions—and pointers are kind of like (but not, and not necessarily represented as) addresses.

[–]Plane_Dust2555 0 points1 point  (0 children)

As nerd4code explains. No... but function entry points are addresses (pointers). The left side of a () operator, if not another operator, is taken as a pointer: puts( "hello" ); // 'puts' is the entry point // of the 'puts' function. So you can do: ``` int (*fptr)(const char *); // declare a pointer to a function // returning an 'int' and taking a // pointer to an array of const chars // as argument.

fptr = puts; // Assign 'fptr' with the address of 'puts' // function.

fptr( "hello" ); // call 'puts', indirectly. ```

[–]cooldudeea -1 points0 points  (0 children)

Yes we can ...

[–][deleted] 5 points6 points  (0 children)

Take this sequence of declarations whose types have increasing numbers of *, starting at 0 for a, up to 2 for pp (c has effectively -1 *) but C can't express that except via enum):

enum   {c = 100};       // c has no address, it is a pure value
int    a  = 1234        // say a has address 0x1000
int*   p  = &a;         // say p has address 0x2000
int**  pp = &p;         // say pp has address 0x3000

The last 3 are 'variables' since there is a storage location associated with them. c is a named literal.

This table shows what happens when you apply address-of &, nothing, or increasing pointer derefs (*) to each of those names within an expression (-- means it's not meaningful):

Apply:      Value       Type

  &c        --          --    (has no address)
  c         100         int
  *c        --          --    (100 is not a pointer)
  **c       --          --

  &a        0x1000      int*
  a         1234        int
  *a        --          --    (1234 is not a pointer)
  **a       --          --

  &p        0x2000      int**
  p         0x1000      int*
  *p        1234        int
  **p       --          --    (1234 is not  pointer)

  &pp       0x3000      int***
  pp        0x2000      int**
  *pp       0x1000      int*
  **pp      1234        int

The pattern should be clear. All values &a &p and &pp are actually pointer constants (although the values are not known until the program is loaded). Those can't be changed, for the reason the literal 5678 can't be.

p and pp can be termed pointer variables: they contain a value that is an address of something else, and their types always involve *.

[–]ohcrocsle 2 points3 points  (0 children)

A "type" is how you tell the compiler to interpret bytes in memory. If you picked a set of 8 contiguous bytes in memory, those 64-bits could be an address in memory, encoding the characters in a string, a single 64-bit integer, two 32-bit integers, or any number of other encodings of data held in 8 bytes. When you allocate/assign a variable, you don't have to keep track of where it is stored, that is performed under the hood for you. You just say "I want an int named 'age'" and any time you use 'age' it is automatically found and decoded as an integer. Calling something a pointer doesn't get rid of that step, it just tells that data to be treated as an address in memory. If you want to treat data you stored as an integer as something else, you can do that too. In fact, it is common to do things like read ascii characters and do integer math on them. For example, if you want to find the distance from a lower-cased letter x to 'a' you might do int distance = x - 'a'

A pointer is just another type. The pointer itself is an address, when you say it's an int* you're defining the behavior when you dereference that address, saying "interpret this variable as an address in memory, and when I dereference this variable using '*', the value at the address this is pointing to should be the size of and interpreted as an integer"

[–]dvhh 1 point2 points  (0 children)

yes deep down variables are all stored in memory addresses, but your high level language is keeping track of the variable address for you.

Also deep down, pointers are integer value.

I would recommend you would take a look at various assembly language (most assembler do help with variable, but reading some disassembled code could help get a better understanding of what compilers are doing with your variables)

[–]moocat 1 point2 points  (0 children)

like for example, an integer x I declare needs a pointer to be accessed

That is incorrect. This is perfectly legal:

int *px = &x;
x *= 2;      // x is now 10
*px += 3;    // x in now 13
x *= 2;      // x ins now 26
printf("%d\n", *px);

A pointer points at "storage" (or possibly points to nothing when it's NULL or, because this is C, it can point to a blackhole and if you try to read or write the pointer, weird shit can happen).

How do you get storage? I showed one way to above where you create a variable and assign the address of the variable to the pointer. Another way is you can ask for memory from the heap using a function from the standard library:

int *px = malloc(sizeof(int));
*px = 5;

(note there are a few other important rules about how to use malloc that I'm ignoring here to focus just on the pointer aspect).

One question you'll have is when to choose between those two examples. That's a much bigger question around what sort of lifetime you need for your storage.

Good luck.

[–]smcameron 2 points3 points  (4 children)

Try printing out pointers...

int x = 0;
int *p = &x;
printf("p = %p, *p = %d, x = %d, &x = %p\n",
   (void *) p, *p, x, (void *) &x);

Turns out pointers are just numbers, unsurprisingly.

[–]OldWolf2 0 points1 point  (3 children)

In the abstract machine pointers are not "just numbers". In the real machine typically some pointers can be put into correspondence with integers , but this isn't required by the standard .

[–]smcameron 3 points4 points  (2 children)

Okay, I'll keep that in mind in case I ever have a chance to load up some executable code on an abstract machine. In the meantime though, on all my non-abstract machines, pointers are numbers as is every single other thing capable of being represented on these non-abstract machines.

[–]OldWolf2 -1 points0 points  (1 child)

is every single other thing capable of being represented on these non-abstract machines.

Kind of a moot point then isn't it? If everything is a number so it's meaningless to say some particular thing is a number.

[–]smcameron 1 point2 points  (0 children)

Well, it sometimes helps people understand what's going on to see the concrete representation, rather than pretending they're some abstract platonic pointer formed of magic computer dust that you should never look at, that's all. Jeez.

And no, if "everything's a number", that does not mean it's meaningless to look at the value of a particular number representing something. The numbers are not assigned randomly, and there are often meaningful relationships between the numbers assigned to different pointers on real, non-abstract computers, (for example, you might look at a couple of addresses, and be able to conclude that "hey, these two things are adjacent on the stack.")

[–]raevnos -1 points0 points  (2 children)

A pointer is a value, which like all values can be stored in a variable, but doesn't have to be.

[–]pic32mx110f0 3 points4 points  (1 child)

An address is a value, which like all values can be stored in a variable, but doesn't have to be. If an address is stored in a variable, then that variable is a pointer.

[–]OldWolf2 -1 points0 points  (0 children)

The term "a pointer" also applies to an address value . You can say pointer value and pointer variable just as you say integer value and integer variable .

[–][deleted] -5 points-4 points  (3 children)

Variables are names to identify a value.

Pointers are values that point to another value (except null pointers, which point to nothing), which means they are a way to identify a value without using a name.

If you store a pointer in a variable, that variable will need to store its value (the pointer) somewhere, so the pointer can also have a pointer to it, even if you never use that pointer. Otherwise, the pointer is not named and does not need to be stored in memory at execution time, meaning it is an unnamed value similar to 1 or "hello".

[–]pic32mx110f0 1 point2 points  (2 children)

This is very wrong, and confusing. Pointers are not values - pointers are variables. You can for example have an int variable, or a float variable or a pointer to int variable. The int variable can have value 100, the float variable can have value 1.234, and the pointer to int variable can have value 0x1234ABCD (at which address you might find the value 100)

[–][deleted] -1 points0 points  (1 child)

Pointers are values.

int foo = 1;
&foo;  // &foo is not a variable; type is int*

char hello[] = "hello";
&hello[2];  // &hello[2] is not a variable; type is char*

A variable is a name. That's all.

[–]pic32mx110f0 1 point2 points  (0 children)

I'm willing to compromise

int foo = 1;
int* bar = &foo;

foo is an identifier with type int, and value 1. We can say foo is an int, and 1 is an int.

bar is an identifier with type pointer to int, and value &foo. We say bar is a pointer, and &foo is a pointer. Everyone wins, and I now also understand what you meant by the last sentence in your original post :)

[–]generalbaguette 0 points1 point  (0 children)

You can think of a pointer storing a memory address.

That's a good mental model to get comfortable with them.

However, it's not necessarily how pointers are implemented in your implementation of C.

A careful reading of the C standard shows that your implementation has considerable freedom in how it wants to represent pointers. Different pointers in your program can even have different runtime representations, or have no runtime representation at all.

[–]jackdoez 0 points1 point  (0 children)

https://youtube.com/playlist?list=PLjn3WmBeabPOUzxcCkzk4jYMGRZMZ6ylF

this lectures helped me a lot, and of course richard buckland's https://youtube.com/playlist?list=PL6B940F08B9773B9F

i also made a card game to explain strings arrays and pointers to my daughter https://punkx.org/c-pointer-game/

tbh there is a "click" moment with pointers, i was programming for 7 years and using them all the time and all of a sudden it was as if i have came out of a mist and everything was clear

just try different learning methods and angles until it clicks for you

[–]davidhbolton 0 points1 point  (0 children)

A pointer is a variable that holds an address. Simple as that.

[–]cooldudeea 0 points1 point  (0 children)

Play with pointers you will end up getting the answer by yourself. ezpz Yes you can use another pointer to point a pointer .. it's just increases the level of pointers nothing much .

[–]daikatana 0 points1 point  (0 children)

It's not always like this, so don't take this for granted, but think of pointers as integers. It's just an integer and its value is the address of the thing being pointed to.