csemantics

Perceived changes in the last decade (or more?) with respect to the semantics of char** and char*[] declarations in the c language


compiler context

First off, I'm currently using Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.7.5. (Just in case this question is more about the tools I'm using than the language and I'm mistaken when suggesting this is about the c language itself.)

question foundation

Case 1 below gives this error: expression must be a modifiable lvalue

/* case 1 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char* defaultlist[] = { "string 1", "string 2" };
char** getlist() { return NULL; }
int main( int argc, char* argv[] ) {
    char* list[]= getlist();      /* line with declaration I'm accustomed to using in past */
    if ( list == NULL )
        list = defaultlist;       /* line with error message */
    return 0;
}

Case 2 is fine:

/* case 2 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char* defaultlist[] = { "string 1", "string 2" };
char** getlist() { return NULL; }
int main( int argc, char* argv[] ) {
    char** list= getlist();       /* modified declaration */
    if ( list == NULL )
        list = defaultlist;       /* no error, anymore */
    return 0;
}

In my ancient past there was no semantic difference whatsoever between a declaration that says char** p and one that says char* p[]. They had identical semantics in all declaration contexts in the 1970's, 1980's, and 1990's when I spent more time with c, than of late. (If I'm wrong about that, I'd like a stern correction and an example demonstrating the difference.)

I'm now observing a complaint from the compiler that suggests a new semantics I've not been accustomed to, previously. It seems that now, c compilers have decided that char* list[] is really char* (* const list). Which isn't my intention.

question

The following points should be addressed:


Solution

  • In my ancient past there was no semantic difference whatsoever between a declaration that says char** p and one that says char* p[]. They had identical semantics in all declaration contexts in the 1970's, 1980's, and 1990's when I spent more time with c, than of late

    It was never the truth. The difference is the same from the beginning of the C language.

    Arrays can't be assigned (ie used as lvalues), pointers can.

    Both lines will not compile:

    char* list[]= getlist(); 
    list = defaultlist; 
    

    Your confusion is a widespread beginner's misunderstanding of arrays and pointers.

    Arrays decay to pointers when used in as rvalues, but they are not pointers. Same happens when you pass the array to a function - you actually pass the pointer to the first element of the array). The confusion comes from the way you can declare function parameters:

    void foo(int arr[], int *arr1[]);
    

    They look like arrays but they are pointers.

    When was the change made, assuming I am right that a change was made in the standard?

    It was always like this. Arrays and pointers were always not the same

    char* p[], in certain circumstances I've yet to learn about, are actually taken as char* (* const p)

    No, you are 100% wrong. You cant assign arrays, but not because they are const

    Am I mistaken in asserting the declaration semantics of char** p and char* p[] were at some point identical?

    No, they were never the same.

    You are probably confused by this ancient way of declaring parameters:

    void foo(p)
    int p[];
    {
        int arr[5];
        p = arr;
    }
    

    It looks like you assign array p, but p (because it is a parameter) is a pointer despite the confusing syntax.