Объявление, управление и доступ к невыровненной памяти в 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-битным словам. Избегайте этого в критичном по скорости коде. Обычно о вас заботятся и это не проблема.