Установка буфера 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;
}

Существуют специальные типы указателей в С++, размер которых больше размера адреса, но вам нужно только учитывать их, когда ссылаетесь на метод класса с несколькими базами.