It is with a star - demonic designs can take the form

const char* const* blah_blah ,

char const* blah

int const* const integer ,

int* const* const and so on in a variety of combinations. How to decipher these outlines, and why would anyone need to turn around in const like a silkworm?

3 answers 3

Such records are most easily viewed from right to left. For example,

This announcement

 const char * const * blah_blah; 

declares a pointer named blah_blah , which points to a constant pointer (since the const qualifier is preceded by *, when viewed from right to left), which points to a constant object of type char .

Here is an example of a simple program that makes it easier to understand this ad.

 #include <stdio.h> int main(void) { const char * const * blah_blah; const char *p = "Hello"; blah_blah = &p; puts( *blah_blah ); } 

The console will display

 Hello 

You can enter typedef names for clarity

 typedef const char CONST_CHAR_TYPE; typedef CONST_CHAR_TYPE *POINTER_TO_CONST_CHAR_TYPE; const POINTER_TO_CONST_CHAR_TYPE *blah_blah; 

in this case, the variable blah_blah is a pointer to a constant pointer to a constant object of type char .

I think it will be easier to understand these declarations if you follow the grammar of the pointer definition.

The pointer in standard C is defined as follows.

 pointer: * type-qualifier-listopt * type-qualifier-listopt pointer 

that is, the list of qualifications that apply to it may follow the * sign.

Therefore, the previous announcement can be written as

 const char ( * const ( *blah_blah ) ); 

You can make the pointer blah_blah constant. But then it must be explicitly initialized when it is declared, if it has an automatic memory duration. for example

 const char ( * const ( * const blah_blah ) ) = /* некоторое значение */; 

or

 const char * const * const blah_blah = /* некоторое значение */; 

Here is a demo program in which the blah_blah pointer blah_blah also declared as constant.

 #include <stdio.h> int main(void) { const char *p = "Hello"; const char ( * const ( * const blah_blah ) ) = &p; puts( *blah_blah ); } 

The declaration of this pointer is read as a constant pointer to a constant pointer to a constant object of type char .

In the function declarations having a prototype, you can omit the parameter identifier.

Below is such a program.

 #include <stdio.h> void display( const char ( * const ( * const ) ) ); void display( const char * const * const ); void display( const char ( * const ( * ) ) ); void display( const char * const * ); int main(void) { const char *p = "Hello"; display( &p ); } void display( const char * const * const blah_blah ) { puts( *blah_blah ); } 

Since there can be several declarations of the same function (without its definition), all the above functions declare the same function. The so-called top-level qualifier that relates directly to a parameter can be removed from the function declaration. That is (another example) these pairs of functions declare the same functions.

 void f( const int x ); void f( int x ); 

and

 void g( int * const p ); void g( int * p ); 

    In C and C ++, the [informal / semi-formal] concept of an access path to an object is often used. Namely: a pointer (or reference) to an object is a way to access this object. Access paths are constant and non-constant . It is impossible to modify an object through constant access paths, through non-constant ones - it is possible (provided that the object itself is modifiable). I can lead both constant and non-constant access paths to the same object.

     int a = 42; int *pa = &a; const int *ca = &a; // `*pa` - неконстантный путь доступа к `a` // `*ca` - константный путь доступа к `a` 

    The examples you cited for using the const modifier at deeper nesting levels in pointer declarations (that is, to the left of * ) just control the constancy of the access path , that is, allow or prohibit the modification of the specified object through this access path .

    (Only your last example comes out of the row, where the rightmost const determines the constancy of the integer , and not the access path to the specified object.)

    If the same object is visible to you through both the constant and non-constant access paths, then a modification of this object made through the non-constant access path will be immediately visible through the constant access path

     int a = 42; int *pa = &a; const int *ca = &a; *pa = 5; // Теперь значение `*ca` равно 5 

    That is, your const modifiers do not mean that the specified object cannot be changed. They just do not allow you to modify the specified object through this access path . Thus, constancy access path management is basically a means of self-discipline. However, in the code, from the very beginning, correctly and actively using the constancy of objects, it is impossible to do without using this tool.

    The rules for constant correctness of C and C ++ languages ​​allow you to freely convert non-constant access paths into constant ones.

     int a = 42; int *pa = &a; // `*pa` - неконстантный путь доступа к `a` const int *ca = pa; // `*ca` - константный путь доступа к `a` 

    but the reverse transformation is impossible. (By using const_cast or C-style cast, you can turn the constant access paths into non-constant ones by “brute force”, but that's another story.)

    It may additionally be noted that the rules of constant correctness of C and C ++ languages ​​are somewhat different, which leads to a number of “illogicalities” that remain to this day in C, which have long been eliminated in C ++

     int **p = 0; const int *const *ccp = p; // Ошибка в С, разрешено в С++ int a[10] = { 0 }; const int (*сpa)[10] = &a; // Ошибка в С, разрешено в С++ 

    This at times makes it difficult to fully use constant access paths to S.

      Well, see, for example:

       int j; // Переменная типа int, в нее можно писать, что угодно. int * pi; // Указатель на переменную int, в которую можно писать: *pi = 5; const int * cpi; // Указатель на константную переменную int, // которую можно только читать: о = *cpi; // Сам указатель можно менять: cpi = &j; const int * const ccpi; // Константный указатель на константную переменную. // И переменную, на которую он указывает, можно только читать, // и сам указатель нельзя изменять... 

      So more or less clear?

      More complex options may include, for example, a constant pointer to a constant pointer like ccpi above :)

      • A constant pointer to a constant variable means that you cannot change this particular variable by any means under any circumstances (what an important variable)? - m9_psy
      • one
        What can not change either the pointer (it is constant), nor what it points to (a constant variable). Of course, you can get around it - by reducing to a non-constant, but why? ... In principle, an intelligent compiler can, based on such a promise of constancy, significantly optimize the code. - Harry
      • one
        @Harry: In order to optimize the code based on the promises of constancy of the specified objects, the compiler must have a complete understanding of aliasing in the considered section of the code. Without a comprehensive understanding of the aliasing picture, the compiler will not be able to use these const for optimization (for even with the presence of a const specified object may change). And with a full understanding of the aliasing picture, the compiler will be able to optimize the code in the same way even without const . For this reason, the constancy of the specified objects , unfortunately, usually cannot improve the code optimization in any way. - AnT
      • one
        restrict and the strict-aliasing rule improve optimization in such situations. And if they "work", then the useful optimization effect from this does not depend on the presence or absence of const . For this reason, it can be argued that the constancy of the indicated objects is nothing more than a means of self-discipline. - AnT
      • @AnT Yes, yes, restrest is especially often used in C ++ ... - Harry