1

At a low level, the following is a list of differences I've found from looking at a one-dimensional array, and a pointer that references what would be the equivalent of a one-dimensional array. Here is the Compiler Explorer showing the differences I've found, and the code below:

#include <stdio.h>

void function(void) {

    // 1. array has elements stored in contiguous-memory
    //    whereas ptr is single 8-byte memory address value
    int nums[] = {1,2,3};
    int* ptr_nums = &nums[0];

    // 2. compile-time sizeof is different
    //    ptr will always be 8 bytes, arr = unit_size * size
    printf("Sizeof: %zu", sizeof(nums));
    printf("Sizeof: %zu", sizeof(ptr_nums));

    // 3. assignment-to-ptr is valid, but not array
    //    by extension, arithmetic on ptr is allowed, not array
    // nums += 1;   // invalid
    ptr_nums = ptr_nums + 2;

    // 4. string-literal initialization is 'literal' rodata value
    //    for pointer, but contiguous chars in memory for array
    char name[] = "ABC"; // something like: mov $6513249, -24(%rbp)
    char* name_ptr = &name[0]; // will not create string literal
    char* name_ptr2 = "QCI"; // pointer to rodata string literal

    // 5. address-of operator
    // &array returns address of first element
    // &ptr return the address of pointer
    // (which *would not* be the same as the first element of the array if it pointed to that)
    printf("%zd", &nums);
    printf("%zd", &ptr_nums);

}

Are there any other differences that I may be missing?

1
  • @EricPostpischil You are right, I am deleting my comment to avoid confusing other readers. I guess I have a gap in my understanding of how memory is allocated for pointers. A similar initialization for an int would not work. Commented Feb 2, 2021 at 3:07

1 Answer 1

3

I am puzzled by what is the purpose of the question. It’s like asking “what is the difference between an int and a struct” - seems entirely arbitrary and the answer of little use. Arrays are not pointers. That’s all. The decay doesn’t somehow link them inseparably, it’s just a convenience: it just lets you use the name of the array in place of the pointer to the first element of the array, in many contexts where a pointer would fit.

Obviously, such a “decayed” pointer is not an lvalue, so you can’t modify it: it’s phantom. Your question seems to be more about “how do lvalues and rvalues differ, and how can I tell” - and you have clearly answered that. Trying array += 1 and seeing it fail is equivalent to trying 5 += 1. You can’t expect anything else, it’d make no sense. In C, an array is not an lvalue, it’s a sort of a bastard, since once you have it in scope, you can’t use it for much: only sizeof and & of the array itself. For everything else, it decays to an rvalue pointer. Note: not a pointer to rvalue, for you can’t have one. The pointer itself is an rvalue. Eg. &(foo[1]) first decays the array, since it has no other use, and then does pointer arithmetic as-if foo was a pointer. Rvalues are immutable, and have no storage, ie. you can’t take their address, among other things.

Again: an array is not an rvalue. An array is a value with storage, but there’s very little syntax that can actually operate on it. C helps out and decays the array when you attempt to use it as if it were a pointer, but that pointer does not exist as an lvalue that you could change. It’s only an rvalue, synthesized on the fly, just as integer literals synthesize rvalues on the fly: you can use them, but only to the extent that rvalues can be used.

This fundamental difference between rvalues and lvalues is among the foundations of the language, and it’s very hard to make much sense of C without having firm and absolute grasp of that concept

To further confuse things, the array definition syntax doesn’t always define an array. For example:

#include <assert.h>

void foo(int notAnArray[10]) {
  int anArray[10];
  assert(sizeof(notAnArray) != sizeof(anArray));
}

void bar(int *notAnArray) {
  int anArray[10];
  assert(sizeof(notAnArray) != sizeof(anArray));
}

C’s semantics dictate that foo and bar are identical (other than their name): the two are just different syntaxes that have identical meaning. Worse yet, there are cases where the first syntax may arguably have some self-documenting uses, even though it’s otherwise completely bonkers.

Sign up to request clarification or add additional context in comments.

14 Comments

thanks, could you please clarify what you mean by “decayed” pointer is not an lvalue -- you can modify the pointer address itself though, right? Or do you mean the array now isn't an lvalue?
@David542: ptr = array + 2 is legal because it uses array as an rvalue in the right hand side (doing pointer math exactly equivalent to &array[2] - that's literally how the [] operator is defined), but array = array+2 or array += 2 aren't legal because array isn't an lvalue.
lvalues and rvalues are fundamental concepts - look them up. “You can modify the pointer address itself though” - no. You can never do that. You can change the value of a pointer, not its address. A value of a pointer is the address of whatever it points to. The address of a pointer is the immutable storage address where that pointer resides. You can change it just the same as you can’t change the address of an array, or of any other variable in fact. In C and C++ world, references are immutable, and the name of a variable is a reference. It can’t be made to refer to something else. Ever.
On the other hand, there are languages where they got tired of rvalues and being on the pedestal and they are objects with storage and lvalues, too. The literals then become just references to those global objects. Yeah, some languages let you set the value of the global object 42, for example. You can make 42 refer to zero or something else. Fun, eh? If you investigate this you’ll learn that those languages are not esoteric at all. Mainstream, even. But you should enjoy figuring it out on your own! :)
@dxiv The rvalue pointer :). That’s the distinction that gets people. And also the reason for it: it hides the fact that C doesn’t support array types other than letting you allocate some storage and initialize it and determine how big the storage is as long as the original name is in scope. Other than that, C has no support for arrays whatsoever. The term pointer decay is all fine, but it somewhat hides the fact that C knows pointer arithmetic only. Ergo, the pointer decay is the only way to use arrays, but that rvalue pointer is just like any other rvalue pointer, say ((char*)0x01234567).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.