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:

  • Am I mistaken in asserting the declaration semantics of char** p and char* p[] were at some point identical? If so, and if it has always been different even back to the 1978 when I first learned C working on the Unix v6 kernel code, then exactly how has it been different and how might I have detected the difference had I tried to do so? (I have many old compilers here that I can use for testing purposes.)
  • Am I correct in recognizing that the declaration semantics of char** p and char* p[] are now different in the sense that char* p[], in certain circumstances I've yet to learn about, are actually taken as char* (* const p) and that this change occurred in some revision to the c standard? (Or some other semantic meaning I missed considering?)
  • When was the change made, assuming I am right that a change was made in the standard?
2

There are 2 best solutions below

6
gulpr On BEST ANSWER

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.

  • char **p; declares a pointer to pointer to char.
  • char* defaultlist[] = { "string 1", "string 2" }; defines the array of two pointers to char.

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.

5
John Bollinger On
  • Am I mistaken in asserting the declaration semantics of char** p and char* p[] were at some point identical? If so, and if it has always been different even back to the 1978 when I first learned C working on the Unix v6 kernel code, then exactly how has it been different and how might I have detected the difference had I tried to do so? (I have many old compilers here that I can use for testing purposes.)

char **p and char *p[] always were and still are equivalent for declaring function parameters. In that context, they are also equivalent to char *p[1] and char *p[10000]. This is because a declaration of a function parameter as an array is automatically "adjusted" to be a declaration of that parameter as a pointer to the erstwhile array's element type. This dovetails with the fact that arrays presented as function arguments (or appearing in most other contexts in expressions) are automatically converted to pointers in an analogous way. These details are described in the 1st edition of K&R (copyright 1978), and they were already well established by then.

  • Am I correct in recognizing that the declaration semantics of char** p and char* p[] are now different in the sense that char* p[], in certain circumstances I've yet to learn about, are actually taken as char* (* const p) and that this change occurred in some revision to the c standard?

char **p and char *p[] were never equivalent for declarations other than function parameters. The former declares a pointer to a char *. The latter declares an array of char * of unspecified length. Given the extent of your experience with C, I would have supposed that you understood well that although they are related, arrays and pointers are completely different kinds of objects. Your remark about char* (* const p) leaves me a bit uncertain about that, though.

All this too is documented in the original K&R, and was well established before then.

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

All versions of ISO C agree with K&R C on these points.