Введение
Недавно ко мне подошли с вопросом, «что это и как этим пользоваться?», показав следующий код:
extern "C" {
void byteMaskDowngrade(byte***const byteMask, const byte *const *const && source) {
// какой-то код.
}
// некоторые фукции.
}
Этим человеком был мой коллега по работе и мы не сразу поняли, что в точности означает параметры в объявлении функции (для тех кому интересно, где вообще может понадобиться такое объявление: в криптографии).
И в упреждение возможных проблем у коллег по цеху, я решил создать данную статью, полагая её в качестве шпаргалки, а именно ответов на два вопроса:
- Как правильно писать такие вот объявления?
- И как их правильно читать?
"*"
Случай с одной звёздочкой самый распространённый, однако и здесь возможны недопонимания:
- Как правильно:
- Так:
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]++; // недопустимо.
}
Комментарии (19)
AEP
23.09.2019 23:46+1Чтобы читать такое, давным-давно придумали утилиту cdecl. И еще вот web-версия на JavaScript.
Taraflex
24.09.2019 06:32В виде расширения vscode
marketplace.visualstudio.com/items?itemName=kaelin.vscode-cdecl
TargetSan
24.09.2019 11:15Это просто шикарно — отдельная утилита чтобы парсить объявления типов в человекочитаемый вид.
thatsme
24.09.2019 14:17void byteMaskDowngrade(byte***const byteMask, const byte *const *const && source);
syntax error
void byteMaskDowngrade(byte***const byteMask, const byte *const *const && source){}
bad character '{'
loginsin
24.09.2019 00:21+2А еще давным давно изобрели typedef. Лучше лестница из typedef'ов, чем это.
Если только целью не была обфускация.
Wyrd
24.09.2019 01:03+1На всякий случай, если вдруг прошло мимо вас, есть замечательное «правило чтения по спирали»
merhalak
24.09.2019 02:32Меня больше интересует другой вопрос: размеры структур по указателям какой размер имеют? И нет ли там в коде выхода за границы массива?
yaroslavche
Крайне интересно! Но зачем? Какой тайный смысл этого, и можно ли обойтись без указателя на указатель на указатель?
dipsy
Особенно второй параметр интересен, rvalue-ссылка на указатель это вообще зачем? Или это какой-то особый диалект языка, ссылка на ссылку?
Tsvetik
Тоже не пойму что это за && в Си
mayorovp
extern "C"
намекает, что это не Си, а С++. А в плюсах&&
как тип данных имеет только одно значение.dipsy
extern "C"
вроде же как разнамекаетпрямо говорит, что функция использует соглашения линковки С, обычно она где-то в *.c файле реализована. Хотя ничего не мешает наверное и С++-функцию так объявить.mayorovp
Где функция реализована — совершенно не важно. Важно лишь, что компилятор Си посчитает такую конструкцию синтаксической ошибкой — а значит, если я вижу
extern "C"
, значит я смотрю на C++.mayorovp
Константная rvalue-ссылка на указатель! Два раза "это вообще зачем?"...
old_gamer
3х мерный массив? )
просто подумал, для чего в принципе можно это использовать…