Константные указатели на указатели на указа…

в 20:34, , рубрики: c++, const, С++, указатели

Введение

Недавно ко мне подошли с вопросом, «что это и как этим пользоваться?», показав следующий код:

extern "C" {
    void byteMaskDowngrade(byte***const byteMask, const byte *const *const && source) {
        // какой-то код.
    }

    // некоторые фукции.
}

Этим человеком был мой коллега по работе и мы не сразу поняли, что в точности означает параметры в объявлении функции (для тех кому интересно, где вообще может понадобиться такое объявление: в криптографии).

И в упреждение возможных проблем у коллег по цеху, я решил создать данную статью, полагая её в качестве шпаргалки, а именно ответов на два вопроса:

  1. Как правильно писать такие вот объявления?
  2. И как их правильно читать?

"*"

Случай с одной звёздочкой самый распространённый, однако и здесь возможны недопонимания:

  • Как правильно:
    • Так:
      const byte p;
    • Или так:
      byte const p;

  • Или в случае с записью '*':
    • Так:
      byte *const p;
    • Или так:
      const byte *p;

Строго говоря, с точки зрения компилятора, наличие звёздочки и её положения в выражении ставит вопрос ребром, т.к. итоговый смысл записи будет отличаться. Наличие спецификатора const в выше указанном случае не имеет никакой разницы при учёте его позиции относительно типа данных, однако ситуация кардинально меняется, когда появляются указатели: один, два, три… ну вы поняли:

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]++; // недопустимо.
}

Всё это выглядит интереснее, когда появляется второй указатель (здесь уже начинает прослеживаться эффективное для чтения правило записи):

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]++; // недопустимо.
}

Как очевидно двойной указатель может быть представлен аж 8-мью различными способами, каждый из которых определяет особый способ использования целевых данных.

Правила чтения подобных выражений следующие:

  • ищем в выражении знак '=' и читаем выражение справа налево;
  • пропускаем имя переменной;
  • далее встречам либо '*', что означает обычный указатель, либо '*const' — константный указатель;
  • таким образом читаем до тех пор пока не встретится тип данных (byte);
  • и последним словом слева от типа данных может быть const, наличие которого означает, что вся эта конструкция ссылается на данные которые нельзя изменять; если же const нет — то можно.

Подобная форма записи и чтения позволяет с лёгкостью читать и понимать даже самые изысканные выражения)

Вот Вам для примера полный набор выражений с тройным указателем:

"***"

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]++; // недопустимо.
}

Автор: AlexeiBulgakov

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js