Constant pointers to pointers to pointers ...

Introduction



Recently, they approached me with the question, β€œwhat is it and how to use it?”, Showing the following code:



extern "C" { void byteMaskDowngrade(byte***const byteMask, const byte *const *const && source) { // - . } //  . }
      
      





This colleague was my work colleague, and we did not immediately understand what exactly the parameters mean in the function declaration (for those who are interested where such an announcement might be needed: in cryptography).



And in anticipation of possible problems from colleagues in the workshop, I decided to create this article, putting it as a cheat sheet, namely the answers to two questions:



  1. How to write such ads here?
  2. And how to read them correctly?


"*"



The case with one asterisk is the most common, however, misunderstandings are also possible here:





Strictly speaking, from the point of view of the compiler, the presence of an asterisk and its position in the expression raises the question since the final meaning of the entry will be different. The presence of the const specifier in the above case does not make any difference when taking into account its position relative to the data type, however, the situation changes dramatically when pointers appear: one, two, three ... well, you understand:



 void p() { //   . byte * a = new byte{1}; a++; // . a[0]++; // . //    . const byte * b = new byte{1}; b++; // . //b[0]++; // . //    . byte *const c = new byte{1}; //c++; // . c[0]++; // . //     . const byte *const d = new byte{1}; //d++; // . //d[0]++; // . }
      
      





All this looks more interesting when the second pointer appears (here an efficient reading rule for writing is already beginning to be traced):



 void pp() { //     . byte ** a = new byte * { new byte{1} }; a++; // . a[0]++; // . a[0][0]++; // . //      . const byte ** b = new const byte * { new byte{1}}; b++; // . b[0]++; // . //b[0][0]++; // . //      . byte *const * c = new byte * { new byte{1}}; c++; // . //c[0]++; // . c[0][0]++; // . //      . byte * *const d = new byte * { new byte{1}}; //d++; // . d[0]++; // . d[0][0]++; // . //       . const byte *const * e = new const byte *const { new byte{1}}; e++; // . //e[0]++; // . //e[0][0]++; // . //       . const byte * *const f = new const byte * { new byte{1}}; //f++; // . f[0]++; // . //f[0][0]++; // . //       . byte *const *const g = new byte *const { new byte{1}}; //g++; // . //g[0]++; // . g[0][0]++; // . //        . const byte *const *const h = new const byte *const { new byte{1}}; //h++; // . //h[0]++; // . //h[0][0]++; // . }
      
      





As you can see, a double pointer can be represented in as many as 8 different ways, each of which defines a special way of using the target data.



The rules for reading such expressions are as follows:





This form of writing and reading makes it easy to read and understand even the most sophisticated expressions)



Here's an example of a complete set of expressions with a triple pointer:



"***"



 void ppp() { //       . byte *** a = new byte * * { new byte * {new byte{1}} }; a++; // . a[0]++; // . a[0][0]++; // . a[0][0][0]++; // . //        . const byte *** b = new const byte * * { new const byte * {new byte{1}} }; b++; // . b[0]++; // . b[0][0]++; // . //b[0][0][0]++; // . //        . byte*const * * c = new byte *const * { new byte *const {new byte{1}} }; c++; // . c[0]++; // . //c[0][0]++; // . c[0][0][0]++; // . //        . byte * *const * d = new byte * *const { new byte * {new byte{1}} }; d++; // . //d[0]++; // . d[0][0]++; // . d[0][0][0]++; // . //        . byte *** const e = new byte * * { new byte * {new byte{1}} }; //e++; // . e[0]++; // . e[0][0]++; // . e[0][0][0]++; // . //         . const byte *const * * f = new const byte *const * { new const byte *const {new byte{1}} }; f++; // . f[0]++; // . //f[0][0]++; // . //f[0][0][0]++; // . //         . const byte * *const * g = new const byte * *const{ new const byte * {new byte{1}} }; g++; // . //g[0]++; // . g[0][0]++; // . //g[0][0][0]++; // . //         . const byte * * *const h = new const byte * *{ new const byte * {new byte{1}}}; //h++; // . h[0]++; // . h[0][0]++; // . //h[0][0][0]++; // . //         . byte *const * *const i = new byte *const * { new byte *const {new byte{1}}}; //i++; // . i[0]++; // . //i[0][0]++; // . i[0][0][0]++; // . //         . byte * *const *const j = new byte * *const { new byte * {new byte{1}}}; //j++; // . //j[0]++; // . j[0][0]++; // . j[0][0][0]++; // . //         . byte *const *const * k = new byte *const *const {new byte *const{new byte{1}}}; k++; // . //k[0]++; // . //k[0][0]++; // . k[0][0][0]++; // . //           const //           . const byte *const *const *const m = new const byte *const *const {new const byte *const {new byte{1}}}; //m++; // . //m[0]++; // . //m[0][0]++; // . //m[0][0][0]++; // . }
      
      






All Articles