Как передать 'char * data', когда данные хранятся как вектор uint8_t? - PullRequest
3 голосов
/ 04 июня 2019

У меня есть определенный класс:

class sco
{
private:

public:
    vector<uint8_t> data;

    sco(vector<uint8_t> data);
    ~sco();

};

Где конструктор:

sco::sco(vector<uint8_t> data) {       
    this->data = data;
}

У меня есть функция, которая объявлена ​​так:

void send(unsigned& id, char* data, char len);

Моя проблема в том, что мне нужно передать в него данные члена sco, но разница типа и указателя сбивает меня с толку. Если у меня есть член newSco с некоторыми данными, было бы разумно назвать эту функцию отправки как send(someId, (char*)&(newSco.data.begin()), newSco.data.size() );? Обратите внимание, что функция send предназначена для микроконтроллера, и она принимает тип char, поэтому я не могу это изменить, и я не могу изменить uint8_t, так как это тип, поступающий из последовательной связи. Я потратил впустую более 3 дней, пытаясь преобразовать типы во что-то взаимное, просто чтобы обратить это назад, потому что это разрушило все. Я сдаюсь и больше не буду пытаться манипулировать типами, поскольку у меня просто нет такого времени, и мне просто нужно, чтобы оно работало, даже если это плохая практика. Я думал, что uint8_t и char имеют одинаковый размер, поэтому это не должно иметь значения.

Ответы [ 5 ]

6 голосов
/ 04 июня 2019
send(someId, (char*)&(newSco.data.begin()), newSco.data.size() )

У вас это почти получилось, но не совсем.

Вот почему:

  • begin() дает вам итератор.Вы берете адрес этого итератора, так что вы на некотором уровне косвенности.Использование приведения в стиле C маскирует то, что в противном случае было бы ошибкой компиляции, связанной с типом.

    Мы могли бы вместо этого написать (char*)&*(newSco.data.begin()), чтобы разыменовать итератор, а затем взять адрес полученного первого элемента.

  • Но если контейнер был пуст, это очень сломано.Вы не можете разыменовать вещь, которая не существует.

Так что теперь мы пытаемся:

send(someId, (char*)&newSco.data[0], newSco.data.size() )

К сожалению, это также небезопасно, если контейнер пуст, поскольку .data[0] также эффективно разыменовывает элемент, который может не существовать.Некоторые утверждают, что подразумеваемое &* «отменяет это», но это противоречиво, и я никогда не верил этому.

Если вы когда-нибудь перейдете на C ++ 11 или более позднюю версию, вы можете использовать совершенно безопасный метод:

send(someId, (char*)newSco.data.data(), newSco.data.size() )

В противном случае используйте &newSco.data[0] , но пропустите весь вызов send, когда newSco.data.size() равен нулю .Не могу этого подчеркнуть.

Приведение к char* само по себе безопасно;вы можете свободно интерпретировать uint8_t с как char с таким образом;для этого есть особое правило.Я сам использовал этот паттерн несколько раз.

Но, как и выше, бросок в стиле C не идеален.Предпочитаю хороший переосмысление.Я также добавил бы const для хорошей меры (пропустите это, если API вашего MC не разрешает это):

if (!newSco.data.empty())
{
   send(
      someId,
      reinterpret_cast<const char*>(&newSco.data[0]),
      newSco.data.size()
   );
}

Там.Великолепный.10

В качестве заключительного замечания, поскольку API принимает char для конечного параметра, я хотел бы рассмотреть возможность установки верхней границы для размера контейнера.Вы можете запустить функцию в цикле, посылая одновременно CHAR_MAX или newSco.data.size() байтов (в зависимости от того, что меньше).В противном случае, если вы ожидаете больше чем CHAR_MAX элементов в контейнере, вы получите неприятное переполнение!

3 голосов
/ 04 июня 2019

Это должно работать:

send(someId, static_cast<char*>(&newSco.data[0]), static_cast<char>(newSco.data.size()));

Дополнительная информация:

Как преобразовать вектор в массив

В чем разницамежду static_cast <> и кастом в стиле C?

2 голосов
/ 04 июня 2019

Если вы можете использовать хотя бы C ++ 11, класс std::vector предоставляет data(), который возвращает необработанный массив.
Если вы до C ++ 11, боюсь, вам придется повторить над вектором и вручную создайте свой char*.

Но, вы не можете static_cast и unsigned char* до char*. Это не разрешено.
Но вам повезло, char* является исключением, которое не нарушает строгое правило псевдонимов. Таким образом, вы можете использовать reinterpret_cast вместо.

Таким образом, вы можете решить вашу проблему следующим образом:

До C ++ 11 :

std::vector<uint8_t> a; // For the example
a.push_back('a');
a.push_back('b');
a.push_back('c');
a.push_back('d');

char b[a.size()];
for(unsigned int i = 0; i < a.size(); ++i)
{
    b[i] = static_cast<char>(a[i]);
}

После C ++ 11 :

std::vector<uint8_t> a {'a', 'b', 'c', 'd'}; //uint8_t aka unsigned char
char * b = reinterpret_cast<char*>(a.data()); // b is a char* so the reinterpret_cast is safe

Надеюсь, это поможет.

2 голосов
/ 04 июня 2019

Да, в этом случае целесообразно привести указатель на символ *. В общем, вы не можете ссылаться на одну и ту же ячейку памяти с разными типизированными указателями из-за строгих правил наложения имен . Тем не менее, 'char' является особым случаем, поэтому приведение разрешено.

Вы не должны приводить uint32_t * к int32_t, например, хотя это может работать в некоторых случаях.

Редактировать: Как отмечено в комментариях ниже: приведение от uint8_t * к char * может быть хорошим, приведение от итератора - нет. используйте .data () вместо .begin ().

1 голос
/ 04 июня 2019

попробуйте использовать reinterpret_cast.Пример:

#include <iostream>
#include <unistd.h>
#include <vector>

int main(int argc, char const *argv[])
{
    std::vector<uint8_t> v{65,66,67,68,69,70};
    char* ptr = reinterpret_cast<char*>(v.data());

    for (auto i{0}; i < v.size(); i++) {
        std::cout << *ptr++ << std::endl;
    }
    return 0;
}

для вашего случая:

void send(someId, reinterpret_cast<char*>(sco.data.data()), sco.data.size());
...