Установка буфера char * с промежуточным литьем в int *
Я не мог полностью понять последствия того, что я прочитал здесь: Наведение указателя int на char ptr и наоборот
Короче говоря, будет ли это работать?
set4Bytes(unsigned char* buffer) {
const uint32_t MASK = 0xffffffff;
if ((uintmax_t)buffer % 4) {//misaligned
for (int i = 0; i < 4; i++) {
buffer[i] = 0xff;
}
} else {//4-byte alignment
*((uint32_t*) buffer) = MASK;
}
}
Edit
Была длинная дискуссия (это было в комментариях, которые таинственно были удалены) о том, к какому типу должен быть направлен указатель, чтобы проверить выравнивание. Тема теперь адресована here.
Ответы
Ответ 1
Это преобразование безопасно, если вы заполняете одно и то же значение во всех 4 байтах. Если byte order
имеет значение, это преобразование небезопасно.
Потому что, когда вы используете целое число для заполнения 4 байта за раз, оно заполняет 4 Bytes
, но порядок зависит от endianness.
Ответ 2
Этот код может помочь вам. Он показывает, что 32-разрядное число создается путем назначения его содержимого байтом за раз, что приводит к смещению. Он компилируется и работает на моей машине.
#include<stdint.h>
#include<stdio.h>
#include<inttypes.h>
#include<stdlib.h>
int main () {
uint32_t *data = (uint32_t*)malloc(sizeof(uint32_t)*2);
char *buf = (char*)data;
uintptr_t addr = (uintptr_t)buf;
int i,j;
i = !(addr%4) ? 1 : 0;
uint32_t x = (1<<6)-1;
for( j=0;j<4;j++ ) buf[i+j] = ((char*)&x)[j];
printf("%" PRIu32 "\n",*((uint32_t*) (addr+i)) );
}
Как уже упоминалось @Learner, нужно подчиняться суждению. Вышеприведенный код не переносится и разбивается на большую конечную машину.
Обратите внимание, что мой компилятор выдает ошибку "cast from" char * to "unsigned int loses precision [-fpermissive]" при попытке применить char * к unsigned int, как это сделано в исходном сообщении. Этот пост объясняет, что вместо этого следует использовать uintptr_t.
Ответ 3
Нет, это не будет работать в каждом случае. Помимо суждения, которое может быть или не быть проблемой, вы предполагаете, что выравнивание uint32_t
равно 4. Но эта величина определяется реализацией (C11 Draft N1570 Раздел 6.2.8). Вы можете использовать оператор _Alignof
, чтобы получить выравнивание переносимым способом.
Во-вторых, эффективный тип (там же, раздел 6.5) местоположения, на который указывает buffer
, может быть несовместим с uint32_t
(например, если buffer
указывает на массив unsigned char
). В этом случае вы нарушаете строгие правила псевдонимов, как только вы попытаетесь прочитать сам массив или указатель другого типа.
Предполагая, что указатель фактически указывает на массив unsigned char
, следующий код будет работать
typedef union { unsigned char chr[sizeof(uint32_t)]; uint32_t u32; } conv_t;
void set4Bytes(unsigned char* buffer) {
const uint32_t MASK = 0xffffffffU;
if ((uintptr_t)buffer % _Alignof(uint32_t)) {// misaligned
for (size_t i = 0; i < sizeof(uint32_t); i++) {
buffer[i] = 0xffU;
}
} else { // correct alignment
conv_t *cnv = (conv_t *) buffer;
cnv->u32 = MASK;
}
}
Ответ 4
В дополнение к конечному вопросу, который уже упоминался здесь:
CHAR_BIT
- количество бит на char
- также следует учитывать.
Это 8 на большинстве платформ, где for (int i=0; i<4; i++)
должно работать нормально.
Более безопасный способ сделать это будет for (int i=0; i<sizeof(uint32_t); i++)
.
В качестве альтернативы вы можете включить <limits.h>
и использовать for (int i=0; i<32/CHAR_BIT; i++)
.
Ответ 5
Используйте reinterpret_cast<>()
, если вы хотите, чтобы базовые данные не меняли форму.
Как отметил ученик, когда вы храните данные в машинной памяти, энтианс становится фактором. Если вы знаете, как данные хранятся правильно в памяти (правильно endianess), и вы специально проверяете его компоновку в качестве альтернативного представления, тогда вы захотите использовать reinterpret_cast<>()
для проверки этой памяти как определенного типа без изменения оригинала хранения.
Ниже я изменил ваш пример, чтобы использовать reinterpret_cast<>()
:
void set4Bytes(unsigned char* buffer) {
const uint32_t MASK = 0xffffffff;
if (*reinterpret_cast<unsigned int *>(buffer) % 4) {//misaligned
for (int i = 0; i < 4; i++) {
buffer[i] = 0xff;
}
} else {//4-byte alignment
*reinterpret_cast<unsigned int *>(buffer) = MASK;
}
}
Также следует отметить, что ваша функция указывает, что буфер (32 байта смежной памяти) равен 0xFFFFFFFF, независимо от того, какая ветка требуется.
Ответ 6
Ваш код идеально подходит для работы с любой архитектурой с 32-разрядной и более поздней версиями. Нет проблемы с порядком байтов, так как все ваши исходные байты 0xFF
.
На компьютерах x86 или x64 дополнительная работа, необходимая для решения проблемы нелицензионного доступа к ОЗУ, управляется процессором и прозрачна для программиста (начиная с Pentium II), при этом с некоторыми затратами на производительность при каждом доступе. Итак, если вы просто устанавливаете первые четыре байта буфера несколько раз, вы можете упростить свою функцию:
void set4Bytes(unsigned char* buffer) {
const uint32_t MASK = 0xffffffff;
*((uint32_t *)buffer) = MASK;
}
Некоторые показания:
Ответ 7
Указатели на примитивные типы в c и С++ указывают на одно и то же место памяти. На некоторых архитектурах вы не можете напрямую обращаться к байту, но в этом случае компилятор будет выполнять некоторые героики, чтобы получить отдельные байты, и вы заметите различия только в том случае, если у вас есть несколько потоков, обращающихся к одному и тому же машинному слову, и в этом случае нет смысла пытаться обращаться к байтам напрямую.
В основном последствия другого связанного с вами потока связаны с тем, что он является указателем байта или указателем на int, что две ссылки указывают на одно и то же местоположение, поэтому трюк, который вы выполняете с отдельными байтами, не требуется.
void set4bytes(unsigned char* pSomeBytes)
{
*((uint32_t *) pSomeBytes) = 0xFFFFFFFF;
}
который сказал, что если вы хотите проверить выравнивание байтов, вы можете наложить указатель на все, что вам нравится, так как вас интересуют только наименее значимые два бита. Здесь я принуждаю адрес к unsigned char - это законно в c и С++, и результат состоит в том, что сохраняются только наименее значимые байты sizeof (unsigned char).
bool is32bitAligned(unsigned char* pSomeBytes)
{
return (((unsigned char)pSomeBytes) & 3) == 0;
}
Существуют специальные типы указателей в С++, размер которых больше размера адреса, но вам нужно только учитывать их, когда ссылаетесь на метод класса с несколькими базами.