Как очистить байты заполнения в структуре для сравнения? - PullRequest
0 голосов
/ 18 ноября 2018

У меня есть структура, определенная следующим образом:

struct s_zoneData {
    bool finep = true;
    double pzone_tcp = 1.0;
    double pzone_ori = 1.0;
    double pzone_eax = 1.0;
    double zone_ori  = 0.1;
    double zone_leax = 1.0;
    double zone_reax = 0.1;
};

Я создал оператор сравнения:

bool operator==(struct s_zoneData i, struct s_zoneData j) {

    return (memcmp(&i, &j, sizeof(struct s_zoneData)) == 0);

}

В большинстве случаев сравнение не удавалось, даже для идентичных переменных. Мне потребовалось некоторое время (и я возился с gdb), чтобы понять, что проблема в том, что байты заполнения для элемента структуры finep являются неинициализированным мусором. Для справки, на моей машине (x64) sizeof(struct s_zoneData) равно 56, что означает, что для элемента finep имеется 7 байтов заполнения.

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

Вопрос в том, есть ли способ указать значение для байтов заполнения для разных компиляторов и платформ? Или, переписав его как более общий вопрос, потому что я мог бы быть слишком сосредоточен на своем подходе, каков будет правильный способ сравнения двух struct s_zoneData переменных?

Я знаю, что создание фиктивной переменной, такой как char pad[7], и инициализация ее нулями должны решить проблему (по крайней мере, для моего конкретного случая), но я читал несколько случаев, когда у людей возникали проблемы с выравниванием структуры для разных компиляторов и порядок членов, поэтому я бы предпочел использовать стандартное решение, если таковое существует. Или, по крайней мере, что-то, что гарантирует совместимость для разных платформ и компиляторов.

Ответы [ 3 ]

0 голосов
/ 18 ноября 2018

В то время как то, что вы делаете, может показаться логичным программисту ac или ассемблера (и даже многим программистам на c ++), вы непреднамеренно делаете, что нарушаете объектную модель c ++ и вызываете неопределенное поведение.

Возможно, вы захотитерассмотреть сравнения типов значений в терминах кортежей ссылок на их элементы данных.

Сравнение двух таких кортежей дает правильное поведение для упорядочивания сравнений, а также равенства.

Они также очень хорошо оптимизируются.

например:

#include <tuple>

struct s_zoneData {
    bool finep = true;
    double pzone_tcp = 1.0;
    double pzone_ori = 1.0;
    double pzone_eax = 1.0;
    double zone_ori  = 0.1;
    double zone_leax = 1.0;
    double zone_reax = 0.1;

    friend auto as_tuple(s_zoneData const & z)
    {
        using std::tie;
        return tie(z.finep, z.pzone_tcp, z.pzone_ori, z.pzone_eax, z.zone_ori, z.zone_leax, z.zone_reax);
    }
};

auto operator ==(s_zoneData const& l, s_zoneData const& r) -> bool
{
    return as_tuple(l) == as_tuple(r);
}

пример вывода ассемблера:

operator==(s_zoneData const&, s_zoneData const&):
  xor eax, eax
  movzx ecx, BYTE PTR [rsi]
  cmp BYTE PTR [rdi], cl
  je .L20
  ret
.L20:
  movsd xmm0, QWORD PTR [rdi+8]
  ucomisd xmm0, QWORD PTR [rsi+8]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+16]
  ucomisd xmm0, QWORD PTR [rsi+16]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+24]
  ucomisd xmm0, QWORD PTR [rsi+24]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+32]
  ucomisd xmm0, QWORD PTR [rsi+32]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+40]
  ucomisd xmm0, QWORD PTR [rsi+40]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+48]
  ucomisd xmm0, QWORD PTR [rsi+48]
  mov edx, 0
  setnp al
  cmovne eax, edx
  ret
.L13:
  xor eax, eax
  ret
0 голосов
/ 18 ноября 2018
  1. #pragma pack может удалить дополнительное заполнение.
  2. Вы можете предотвратить добавление дополнительного заполнения, добавив его вручную, чтобы его можно было явно установить в предварительно определенное значение (но инициализация будет иметьдолжно быть сделано за пределами структуры):


struct s_zoneData {
    char pad[sizeof(double)-sizeof(bool)];
    bool finep;
    double pzone_tcp;
    double pzone_ori;
    double pzone_eax;
    double zone_ori;
    double zone_leax;
    double zone_reax;
};

...
s_zoneData X = {{},true, 1.0, 1.0, 0.1, 1.0, 0.1};

Редактировать : согласно комментарию @Guille, дополнение должно быть связано с boolчлен для предотвращения внутренней прокладки.Таким образом, либо пэд должен быть непосредственно перед / после finep (я изменил сэмпл на это), либо finep должен быть перемещен в конец структуры.

0 голосов
/ 18 ноября 2018

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

...