Объявление, управление и доступ к невыровненной памяти в C++

Недавно я опубликовал вопрос о доступе к памяти без выравнивания, но, учитывая ответ, я немного растерялся. Я часто слышу, что "согласованный доступ к памяти гораздо эффективнее, чем доступ без выравнивания", но на самом деле я не уверен, что такое невыровненная память. Как следствие:

  • Что такое выравнивание памяти?
  • Как вы объявляете что-то не выровненное в C++? (небольшой пример программы)
  • Как вы получаете доступ и управляете чем-то не выровненным в C++? (небольшой пример программы)
  • Есть ли способ манипулировать невыровненной памятью с помощью определенного подхода к поведению, или все это зависит от платформы / неопределенное поведение в C++?

2 ответа

Решение

Вопрос о том, является ли что-то не выровненным или нет, зависит от типа данных и их размера. Как объясняет ответ Грегга.

Хорошо написанная программа обычно не имеет выравниваемого доступа к памяти, за исключением случаев, когда компилятор вводит ее. (Да, это происходит во время векторизации, но давайте пропустим это).

Но вы можете написать программу на C++ для принудительного доступа к памяти без выравнивания. Код ниже делает именно это.

#include <iostream>
using namespace std;
int main() {

  int a[3] {1, 2, 3};

  cout << *((long long *)(&a[0])) << endl;
  cout << *((long long *)(&a[1])) << endl;

  cout <<  (long long) (&a[0]) << endl;
  cout << (long long) (&a[1]) << endl;

  return 0;

}

Вывод кода это

8589934593
12884901890
70367819479584
70367819479588

Что делает эта программа? Я объявляю целочисленный массив размером 3. Этот массив будет выровнен на 4 байта, потому что int является 4-байтовым типом данных (по крайней мере, на моей платформе). Таким образом, адрес a[0] делится на 4. Теперь адрес обоих из a[0] и a[1] делится на 4, но только адрес одного из них делится на 8.

Поэтому, если я приведу адрес a[0] и a[1] к указателю на long long (который является 8-байтовым типом данных на моей платформе), а затем откажусь от этих двух указателей, одним из них будет доступ к памяти без выравнивания., Это не неопределенное поведение AFAIK, но оно будет медленнее, чем выравниваемый доступ к памяти.

Как вы видите, этот код содержит приведения в стиле C, что не является хорошей практикой. Но я думаю, что для навязывания какого-то странного поведения это нормально.

дайте мне знать, если у вас есть вопрос о выводе кода. Вы должны знать о порядке байтов и представления целых чисел, чтобы понять первые две строки. Третья и четвертая строка - это адреса первых двух элементов целочисленного массива. Это должно быть проще для понимания.

На примере 32-битного компьютера, считывающего 4-байтовое слово данных:

В аппаратном обеспечении 32-битный компьютер читает 4 байта за раз, но только на каждый 4-й байт. Это связано с тем, что шина памяти имеет ширину 4 байта.

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

Основываясь на выбранной архитектуре, компилятор знает это и размещает / дополняет структуры данных таким образом, чтобы двухбайтовые данные появлялись на двухбайтовых границах, 4-байтовые данные начинались на 4-байтовых границах и т. Д. Это специально для того, чтобы избежать неправильного чтения.

Вы можете получить неправильное чтение, если прочитаете данные в байтах (например, из последовательного протокола), а затем получите доступ к ним как 32-битным словам. Избегайте этого в критичном по скорости коде. Обычно о вас заботятся и это не проблема.

Другие вопросы по тегам