Хитрый вопрос для разработчика среднего уровня C++

Мне задали этот вопрос на собеседовании, и я не могу понять, что здесь происходит. Вопрос "Что будет отображаться в консоли?"

#include <iostream>

int main()
{
    unsigned long long n = 0;
    ((char*)&n)[sizeof(unsigned long long)-1] = 0xFF;

    n >>= 7*8;

    std::cout << n;
}

Что здесь происходит, шаг за шагом?

Ответ 1

Позвольте получить это один шаг за раз:

((char*)&n)

Это приводит к преобразованию адреса переменной n из unsigned long long* в char*. Это допустимо, и на самом деле доступ к объектам разных типов через указатель char является одним из очень немногих случаев "наказания за тип", принятых языком. По сути, это позволяет вам получить доступ к памяти объекта n в виде массива байтов (он же char в C++)

((char*)&n)[sizeof(unsigned long long)-1]

Вы получаете доступ к последнему байту объекта n. Помните, sizeof возвращает размерность типа данных в байтах (в C++ char имеет альтер-эго байта)

((char*)&n)[sizeof(unsigned long long)-1] = 0xFF;

Вы устанавливаете последний байт n в значение 0xFF.

Поскольку n было 0 изначально, память макета n теперь:

00  .. 00 FF

Теперь обратите внимание на ... Я положил в середине. Это не потому, что мне лень копировать и вставлять значения, которые имеет количество байтов n, а потому, что размер unsigned long long не установлен стандартом в фиксированное измерение. Существуют некоторые ограничения, но они могут варьироваться от реализации к реализации. Так что это первое "неизвестное". Однако на большинстве современных архитектур sizeof (unsigned long long) равен 8, поэтому мы собираемся пойти на это, но в серьезном интервью вы должны упомянуть об этом.

Другое "неизвестное" - как эти байты интерпретируются. Целые числа без знака просто кодируются в двоичном виде. Но это может быть порядок с прямым или обратным порядком. x86 является прямым порядком байтов, поэтому мы приведем его в качестве примера. И снова, в серьезном интервью вы должны упомянуть об этом.

n >>= 7*8;

Это право сдвигает значение n 56 раз. Обратите внимание, сейчас речь идет о значении n, а не байтов в памяти. С нашими предположениями (размер 8, младший порядок) значение, закодированное в памяти, равно 0xFF000000 00000000 поэтому смещение его 7*8 раз приведет к значению 0xFF которое равно 255.

Итак, если предположить, что sizeof(unsigned long long) равен 8 а кодирование с прямым порядком байтов немного печатает, программа выводит 255 на консоль.


Если мы говорим о системе с 0xff байтов, структура памяти после установки последнего байта 0xff остается прежней: 00... 00 FF, но теперь закодированное значение равно 0xFF. Так что результат n >>= 7*8; будет 0. В системе с прямым порядком байтов программа выводит 0 на консоль.


Как указано в комментариях, есть и другие предположения:

  • char 8 бит. Хотя sizeof(char) гарантированно равен 1, он не должен иметь 8 бит. Все известные мне современные системы имеют биты, сгруппированные в 8-битные байты.

  • целые числа не должны быть маленькими или старшими. Могут быть другие схемы расположения, например, со средним порядком байтов. Быть чем-то иным, чем маленький или большой порядок байтов считается сегодня эзотерическим.

Ответ 2

Приведите адрес n к указателю на символы, установите седьмой (предполагая, что sizeof (long long) == 8) элемент char в 0xff, затем сдвиньте результат вправо (как long long) на 56 бит.