Вопрос или проблема
Читая исходный код для RakNet на GitHub, я наткнулся на следующее в RakNetDefines.h
.
typedef unsigned char RPCIndex;
const int MAX_RPC_MAP_SIZE=((RPCIndex)-1)-1;
const int UNDEFINED_RPC_INDEX=((RPCIndex)-1);
Запустив следующий код через онлайн-компилятор C++:
#include <iostream>
typedef unsigned char RPCIndex;
const int MAX_RPC_MAP_SIZE=((RPCIndex)-1)-1;
const int UNDEFINED_RPC_INDEX=((RPCIndex)-1);
int main() {
std::cout << MAX_RPC_MAP_SIZE << " ";
std::cout << UNDEFINED_RPC_INDEX;
return 0;
}
Я получаю 254 255
в качестве вывода. Хотя я знаю, что 255
является (обычно) максимальным значением для unsigned char
в C++, я был шокирован тем, что простое указание имени типа и вычитание одного из него приводит к его максимальному значению. Более того, выражение (RPCIndex)-0
равно 0
. Почему это происходит в C++? Есть ли на это историческая причина, удобно ли это, или я что-то упускаю?
(RPCIndex)-1
анализируется как выражение приведения к типу в стиле C вида (T)E
, где T
— это тип, а E
— унарное выражение.
В этом случае T
— это тип RPCIndex
, а E
— выражение -1
, которое является унарным оператором минус, применяемым к целочисленному литералу 1
.
Результатом является значение -1
, преобразованное в RPCIndex
. Поскольку RPCIndex
является беззнаковым целочисленным типом, результатом будет представимое значение RPCIndex
, конгруэнтное -1
по модулю 2**w
, где w
— это ширина целочисленного типа RPCIndex
. Другими словами, это выдает 2**w-1
, максимальное representable значение беззнакового целочисленного типа.
Я думаю, вы можете увидеть, как работает (RPCIndex)-0
…
Этот трюк с приведением -1
происходит из C, где нет другого способа назвать максимальное значение некоторого псевдонимного беззнакового целочисленного типа, не зная, что он представляет собой, или каково его максимальное значение заранее.
В C++ можно более четко выразить намерение с помощью std::numeric_limits
из <limits>
. Трюк с -1
больше не нужен:
const int UNDEFINED_RPC_INDEX = std::numeric_limits<RPCIndex>::max();
В общем, код больше напоминает C, чем (современный) C++ код, хотя он также работает ожидаемым образом в C++.
Происходит то, что вы выполняете приведение в стиле C. (RPCIndex)-1
говорит о том, чтобы привести -1
, которое является int
, к RPCIndex
. RPCIndex
является псевдонимом для unsigned char
, и ключевым моментом здесь является то, что он беззнаковый.
С беззнаковыми типами они не становятся отрицательными. Вместо этого они оборачиваются по модулю 2n, где n
— это битовая ширина типа. Это означает, что -1
становится 2n – 1, что является максимальным значением для типа.
Ответ или решение
Вопрос о том, почему выражение (RPCIndex)-1
возвращает максимальное значение для типа unsigned char
в C++, требует внимательного рассмотрения механизмов приведения типов и представления данных в языке C++.
Понимание приведения типов
Когда мы пишем (RPCIndex)-1
, это интерпретируется как C-стиль приведения типов, где RPCIndex
является псевдонимом для типа unsigned char
. Оператор -1
сначала вычисляется как целое число со знаком, и затем это значение приводится к типу unsigned char
. Основные моменты здесь заключаются в том, что unsigned char
, как и другие беззнаковые целочисленные типы, не поддерживает отрицательные значения, и все операции происходят в пределах модуля (2^n) (где (n) – размер в битах типа).
Влияние знака и двухкомплементарного представления
При приведении типа происходит следующее:
- Значение
-1
, которое в двоичном представлении записывается как все единицы (например,11111111
дляunsigned char
, который имеет размер 8 бит), преобразуется в беззнаковый тип. - В C++ для беззнаковых типов любое отрицательное значение интерпретируется по модулю (2^n). Конкретно, для
unsigned char
значение-1
становится (2^8 – 1), что равно 255 – максимальное значение, которое может быть представлено в данном типе.
Таким образом, выражение (RPCIndex)-1
эквивалентно 255, поскольку оно преобразует -1
в его соответствующее беззнаковое значение.
Определение констант
Теперь рассмотрим, как используется это приведенное значение в вашем коде. В коде определяются две константы:
const int MAX_RPC_MAP_SIZE = ((RPCIndex)-1) - 1; // Это 254
const int UNDEFINED_RPC_INDEX = ((RPCIndex)-1); // Это 255
MAX_RPC_MAP_SIZE
получает значение 254, потому что 255 (максимальное значениеRPCIndex
) уменьшается на 1.UNDEFINED_RPC_INDEX
получает значение 255, что соответствует максимальному значению типаunsigned char
.
Альтернативные способы получения максимального значения
Данный подход к определению максимальных значений для беззнаковых типов уходит корнями в C, где аналогичного способа не существовало для получения максимального значения без знания внутреннего представления типов. Однако, начиная с C++, более современный подход включает использование std::numeric_limits
из библиотеки <limits>
, что является более понятным и безопасным способом:
#include <limits>
const int UNDEFINED_RPC_INDEX = std::numeric_limits<RPCIndex>::max();
Сейчас использование std::numeric_limits
становится предпочтительным, так как оно более выразительно и позволяет избежать ошибок, связанных с неправильным приведением типов.
Заключение
Таким образом, использование (RPCIndex)-1
в C++ демонстрирует глубокую связь между привидением типов и представлением данных в памяти. Хотя этот подход легитимен и корректен, современные практики программирования рекомендуют использование более безопасных и понятных возможностей, предлагаемых стандартной библиотекой, таких как std::numeric_limits
. Это не только улучшает читаемость кода, но и способствует его поддерживаемости и снижает риск ошибок, связанных с неправильным пониманием работы с типами.