Приведение QByteArray к `long` выводит другой результат для одного и того же ввода

EDIT: добавлен полный пример проекта MCV.

У меня странная проблема, когда один и тот же код и один и тот же ввод дают разные выходные значения.

Цель кода — протестировать функцию, которая принимает значение, упакованное в 4 байта, и распаковывает его в одно 32-битное значение. Ожидаемое значение value1, value2 и value3 в test_unpack() равно 2018915346 (т. е. 0x78563412 из-за распаковки с прямым порядком байтов). Я получил этот метод распаковки из другого ответа. Ниже приведен пример MCV, который вы можете легко построить и увидеть проблему самостоятельно. Обратите внимание, что если вы закомментируете тело test1() test_unpack() волшебным образом перейдет с правильным значением.

test_canserialcomm.cpp

#include "test_canserialcomm.h"

#include <QtTest/QtTest>
#include <QByteArray>

long unpack() noexcept
{
    quint8 a_bytes[] = {0x12, 0x34, 0x56, 0x78};
    QByteArray a = QByteArray(reinterpret_cast<char*>(a_bytes), 4);
    long value1 = *((long*)a.data());
    qDebug() <<  value1; // outputs "32651099317351442" (incorrect value)

    quint8 b_bytes[] = {0x12, 0x34, 0x56, 0x78};
    QByteArray b = QByteArray(reinterpret_cast<char*>(b_bytes), 4);
    long value2 = *((long*)b.data());
    qDebug() << value2; // outputs "2018915346" (correct value)

    quint8 c_bytes[] = {0x12, 0x34, 0x56, 0x78};
    QByteArray c = QByteArray(reinterpret_cast<char*>(c_bytes), 4);
    long value3 = *((long*)c.data());
    qDebug() << value3; // outputs "2018915346" (correct value)

    return value1;
}

void TestCanSerialComm::test1()
{
    QCOMPARE("aoeu", "aoeu"); // If you comment this line, the next test will pass, as expected.
}

void TestCanSerialComm::test_unpack()
{
    long expected {0x78563412};
    QCOMPARE(unpack(), expected);
}

test_canserialcomm.h

#ifndef TEST_CANSERIALCOMM_H
#define TEST_CANSERIALCOMM_H
#include <QtTest>

class TestCanSerialComm: public QObject
{
    Q_OBJECT
private slots:
    void test1();
    void test_unpack();
};
#endif // TEST_CANSERIALCOMM_H

test_main.cpp

#include <QtTest>
#include "test_canserialcomm.h"
#include <QCoreApplication>

int main(int argc, char** argv) {
    QCoreApplication app(argc, argv);
    TestCanSerialComm testCanSerialComm;
    // Execute test-runner.
    return QTest::qExec(&testCanSerialComm, argc, argv); }

tmp.pro

QT += core \
    testlib
QT -= gui
CONFIG += c++11

TARGET = tmp
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
TARGET = UnitTests

HEADERS += test_canserialcomm.h
SOURCES += test_canserialcomm.cpp \
    test_main.cpp

Вывод value1 в test_unpack() неверен, несмотря на тот же код и те же входные данные. Как ни странно, если я удаляю вызовы qDebug() и устанавливаю точку останова, средство оценки выражений отладчика теперь показывает, что value2 имеет неправильное значение.

Любая идея, почему это происходит? Или даже как устранить эту проблему дальше?

Дополнительные примечания. Если я добавлю строку qDebug() << "garbage"; вверху функции, все 3 полученных значения будут правильными.


person DBedrenko    schedule 21.04.2016    source источник
comment
посмотрите на представления значений в памяти, может дать ключ (у меня нет qt) - еще одна мысль: почему бы не использовать для этого простой союз?   -  person slashmais    schedule 21.04.2016
comment
@slashmais Спасибо за совет. Под представлениями в памяти вы имеете в виду значения переменных, которые я вижу в отладчике, когда устанавливаю точку останова? Эти значения совпадают с выводами qDebug(). Я не уверен, как я могу использовать союз в этом случае.   -  person DBedrenko    schedule 21.04.2016
comment
Я запускаю ваш код и получаю одинаковый результат для всех трех значений.   -  person Paraboloid87    schedule 21.04.2016
comment
Что-то еще происходит в вашем коде? Вы можете опубликовать MCV?   -  person Paraboloid87    schedule 21.04.2016
comment
@ Paraboloid87 Благодаря вашим комментариям мне удалось еще больше изолировать проблему и добавить пример проекта MCV, который воспроизводит проблему (я протестировал его).   -  person DBedrenko    schedule 21.04.2016
comment
Какой размер long в вашей системе?   -  person ecatmur    schedule 21.04.2016
comment
Итак, что происходит (игнорируя выравнивание), когда вы пытаетесь использовать псевдоним для 4-байтового массива как 8-байтовый интеграл?   -  person ecatmur    schedule 21.04.2016
comment
@ecatmur Ей-богу! Что происходит, так это то, что когда я привожу к 8 байтам, он читает 4 байта дальше того, что на самом деле содержит QByteArray. Урок заключается в использовании типов с фиксированной шириной, и у меня сработало qint32 вместо long. Если вы опубликуете этот ответ, я приму его :) Спасибо за вашу помощь   -  person DBedrenko    schedule 21.04.2016
comment
@ecatmur Есть ли что-то, что я должен подготовить или знать о выравнивании данных при упаковке/распаковке?   -  person DBedrenko    schedule 21.04.2016
comment
под памятью я имею в виду, что вы смотрите на адреса памяти, содержащие значения / vars - возможно, это помогло вам определить, что предложил ecatmur.   -  person slashmais    schedule 21.04.2016


Ответы (1)


Вы компилируете и запускаете эту программу в системе, где long составляет 8 байтов, а ваша QByteArray имеет только 4 байта. Это означает, что когда вы псевдонимируете массив как long (используя *((long*)a.data())), вы читаете 4 байта после конца массива в неинициализированное хранилище кучи.

Исправление заключается в использовании типа, который гарантированно имеет размер 4 байта, например. std::int32_t.

Кроме того, использование *((long*)[...]) для псевдонимов памяти не гарантирует работу, в первую очередь из-за проблем с выравниванием, а также (в общем случае) из-за того, что псевдонимы поддерживаются только для типов, эквивалентных char или варианту signed или unsigned. Безопаснее использовать memcpy:

std::uint32_t value1;
assert(a.size() == sizeof(value1));
memcpy(&value1, a.data(), a.size());
person ecatmur    schedule 21.04.2016