Приведение bool к 1 или 0 с помощью указателей
Как из true получается 1 при использовании указателей? И почему именно 1 а не другое число как в случае с переменной d?
unsafe
{
bool a = true;
bool* b = &a;
int c = (int*)b; //указатель на число 1, почему?
int d = (int)b; //какое-то другое число, почему?
}
Ответы (2 шт):
Какое-то другое число получается, потому что адрес переменной приводится к инту. В 32-разрядной системе это будет выглядеть примерно как 0x124a53b0
По внутреннему содержанию логические переменные есть 0 и 1, что вы и видите в первом случае
b - это адрес в памяти. При чем bool* это указатель на память, участок памяти размером 1 байт. А int* - указатель на участок памяти в 4 байта. То есть при обращении к участку памяти:
bool a = true;
bool* b = &a;
int number = *(int*)b;
...вы получите не только байт, содержащий bool единичку, но и соседние 3 байта, которые могут содержать вообще что угодно. То есть как повезет, либо там будут нули и вы получите 1, либо там будет что-то другое и вы получите другое число.
При обращении к самому указателю
int address = b;
Вы получите число, являющееся адресом в памяти. При этом приведение типа указателя не имеет значения. А имеет значение разрядность приложения. В x86 приложениях адрес в памяти 32 бита (4 байта), то есть uint, в x64 приложениях в 2 раза больше, то есть 8 байт или ulong. Поэтому для написания универсального кода при работе с адресами в неуправляемой области памяти, придуман класс IntPtr (nint). Он всегда имеет тот размер, который нужен для текущей разрядности приложения.
Если надо преобразовать bool в int, используя указатели, то делать это надо через указатель типа byte.
bool a = true;
bool* b = &a;
int number = *(byte*)b;
Вот так отработает безопасно не цепляя при чтении лишние байты памяти.
В обратном направлении тоже.
int* ptr = &number;
bool c = *(bool*)ptr;
Но и здесь есть нюанс. Этот код сработает не на всех процессорах. Есть формат чисел Big-Endian и Little-Endian. То есть оканчивающиеся старшим байтом и младшим байтом. Выше показанный код сработает только для Big-Endian, где младший байт вначале участка памяти. Чтобы сработало для Little-Endian, надо немного изменить последнюю строчку.
bool c = *((bool*)ptr + 3);
Но лучше для таких простых операций вообще не использовать unsafe, например.
int number = a ? 1 : 0;
Сейчас практически любые трюки можно вытворять с памятью не прибегая к небезопасному коду. Поэтому внутреннюю логику приложения на нем почти никогда не приходится строить.
Кстати, принято считать, что для bool значение false это 0, а значение true это 1. На самом деле это только наполовину правда. На уровне процессооа ветвление кода происходит только с помощью проверки на 0. Например кусок ассемблерного кода.
mov eax, 1
cmp eax, 0
jz [какой-то адрес]
Где jz проверяет ZF флаг процессора, который устанавливается при вызове инструкции cmp проверяющей регистр eax на 0. И если там 0, переход происходит, если нет, то не происходит. Есть еще инструкция jnz, которая инвертирует условие перехода, типа шарпового if (!a).
Другими словами если bool имеет значение false, то это точно 0, а если true, то это любое другое число, но не ноль. Например число 34 - это тоже true.
При работе с небезопасным кодом всю кучу этих нюансов следует учитывать. На самом деле, почти всё зависит от того, каких инструкций нагенерит JIT, поэтому я просто советую запомнить, что 0 - это false.