Передача указателя для размещения в массиве - PullRequest
1 голос
/ 11 февраля 2020

Изучение C и некоторые проблемы с указателями / массивами. Использование MPLAB X и PIC24FJ128GB204 в качестве целевого устройства, но я не думаю, что это имеет значение для этого вопроса. Ответ может быть очевиден, но без особых знаний в C (пока) трудно найти аналогичный вопрос, который, как я понимаю, достаточен, чтобы сделать выводы.

Я написал библиотеку I2 C с следующая функция:

int I2C1_Write(char DeviceAddress, unsigned char SubAddress, unsigned char *Payload, char ByteCnt){
    int PayloadByte = 0;
    int ReturnValue = 0;
    char SlaveWriteAddr;

    // send start
    if(I2C1_Start() < 0){
        I2C1_Bus_SetDirty;
        return I2C1_Err_CommunicationFail;
    }

    // address slave
    SlaveWriteAddr = (DeviceAddress << 1) & ~0x1;       // shift left, AND with 0xFE to keep bit0 clear
    ReturnValue = I2C1_WriteSingleByte(SlaveWriteAddr);
    switch (ReturnValue){
        case I2C1_OK:
            break;
        case I2C1_Err_NAK:
            I2C1_Stop();
            I2C1_Bus_SetDirty;
            return I2C1_Err_BadAddress;
        default:
            I2C1_Stop();
            I2C1_Bus_SetDirty;
            return I2C1_Err_CommunicationFail;

    }

    // part deleted for brevity

    // and finally payload
    for(PayloadByte = 0; PayloadByte < ByteCnt; PayloadByte++){
        // send byte one by one
        if(I2C1_WriteSingleByte(Payload[PayloadByte]) != I2C1_ACK){
            I2C1_Stop();
            I2C1_Bus_SetDirty;
            return I2C1_Err_CommunicationFail;            
        }
    }
    return I2C1_OK;
}   

Я хочу вызвать эту функцию из другой, используя предопределенный констант:

const unsigned char CMD_SingleShot[3] = {2, 0x2C, 0x06};

Это имеет длину команды в качестве первого байта, затем байты команды.

Вызывающая функция:

int SHT31_GetData(unsigned char MeasurementData[]){
    // address device and start measurement
    if(I2C1_Write(SHT31_Address,
                0xFF,
                CMD_SingleShot[1],       // this is where the error message is given
                CMD_SingleShot[0])
                < 1){
        return -1;
    }
    //code omitted for brevity

    return 1;
}

Сообщение об ошибке:

.. / Sensirion_SHT31. c : 40: 17: предупреждение: передача аргумента 3 в 'I2C1_Write' делает указатель из целого числа без приведения

.. / I2C1.h: 66: 5: note: ожидается «unsigned char *», но аргумент имеет введите 'unsigned char'

Проблема явно (unsigned char)CMD_SingleShot[1], где я пытаюсь дать указатель на второй байт массива unsigned char.

Я пробовал:

  • чтение по указателям и массивам и попытка понять
  • searchin g для аналогичных функций
  • разочарованы пониманием и попыткой случайных вещей, надеясь, что сообщения об ошибках приведут меня к правильному способу сделать это. Вещи вроде:
CMD_SingleShot[1]
&CMD_SingleShot[1]
(unsigned char)CMD_SingleShot + 1

это просто выдало другие сообщения об ошибках.

Мои вопросы:

  • при условии, что функция I2C1_Write как есть (ожидание неподписанного char *) (например, если это не мой код, и я не могу его изменить), как бы я передал указатель на второй байт в массиве продолжение без знака? Насколько я понимаю, массив является указателем, поэтому
  • , поскольку это мой код, есть ли лучший способ сделать это вообще?

Ответы [ 3 ]

2 голосов
/ 11 февраля 2020

Во-первых, не выполняйте приведение, если вы не знаете лучше, чем происходит компилятор. Что, в вашем случае, вы не делаете. Здесь нечего стыдиться.

Выполнение &CMD_SingleShot[1] - это шаг в правильном направлении. Проблема в том, что CMD_SingleShot[1] имеет тип const unsigned char и, следовательно, взяв адрес этого, вы получите указатель типа const unsigned char *. Это нельзя передать параметру Payload, так как он ожидает указатель unsigned char *. К счастью, вы не изменяете то, на что указывает Payload, поэтому нет никаких оснований для этого, чтобы быть неконстантным. Просто измените определение Payload на const unsigned char *, и компилятор будет счастлив.

И, кстати, в c, &Foo[n] совпадает с Foo + n. Все, что ты пишешь - дело вкуса.

Редактировать:

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

В вашем случае правильное приведение будет (unsigned char *)&CMD_SingleShot[1] и NOT (unsigned char *)CMD_SingleShot[1]. В первом случае указатель одного типа интерпретируется как указатель другого типа. Второй случай интерпретирует неподписанный символ как указатель, что очень плохо.

1 голос
/ 11 февраля 2020

Вызов функции в основном корректен, но, поскольку 3-й параметр функции является указателем, вы должны соответственно передать адрес в массив, а не один символ. Таким образом, &CMD_SingleShot[1], а не CMD_SingleShot[1].

if(I2C1_Write(SHT31_Address,
                0xFF,
                &CMD_SingleShot[1], 
                CMD_SingleShot[0])
                < 1)

Однако, когда вы делаете это, вы заявляете, что получаете «отбрасывает квалификаторы из указателя цели», что является замечанием о правильности констант - очевидно, CMD_SingleShot const (поскольку это переменная fla sh или что-то подобное?).

Эта ошибка компилятора, в свою очередь, просто означает, что функция неправильно спроектирована - функция записи I2 C явно не должна изменять данные, просто отправьте. Поэтому наиболее правильным решением является изменение функции на const unsigned char *Payload. Изучите const правильность - если функция не изменяет данные, переданные ей указателем, тогда этот указатель должен быть объявлен как const type*, «указатель на данные типа« только для чтения ».

Если было бы невозможно изменить функцию, потому что вы застряли в API, написанном кем-то другим, то вам придется скопировать данные в буфер чтения / записи, прежде чем передавать их в функцию. «Отбрасывание» const почти никогда не бывает правильным (хотя чаще всего работает на практике, но без гарантий).


Другие проблемы:

  • При программировании C в общем, и встроенный C, в частности, вы должны использовать stdint.h вместо типов по умолчанию C, которые проблематичны c, поскольку имеют переменные размеры.
  • Никогда не используйте char (без unsigned) для всего, кроме реальных строк. Он имеет сигнатуру, определяемую реализацией, и обычно опасен - никогда не использует его для хранения необработанных данных.
  • При программировании 8-битного MCU всегда используйте uint8_t / int8_t, когда вы знаете, заранее, что переменная не будет содержать больших значений, чем 8 бит. Во многих случаях компилятор просто не может оптимизировать 16-битные значения до 8-битных.
  • Никогда не используйте подписанные или потенциально подписанные операнды для побитовых операторов. Код, такой как (DeviceAddress << 1) & ~0x1, чрезвычайно опасен. Мало того, что DeviceAddress потенциально подписано и может оказаться отрицательным, оно неявно повышается до int. Точно так же 0x1 имеет тип int и т. Д. 2 дополнения PI C ~0x1 фактически сводится к -2, что не то, что вы хотите.

    Вместо этого попробуйте u суффикс все целочисленные константы и изучение Неявные правила продвижения типов .

1 голос
/ 11 февраля 2020

Передача адреса второго байта вашей команды выполняется с помощью

&CMD_SingleShot[1]

или

CMD_SingleShot+1

Но тогда вы столкнетесь с недопустимым преобразованием ошибка, поскольку ваша команда определена как const unsigned char, а затем &CMD_SingleShot[1] имеет тип const unsigned char*, но ваша функция ожидает unsigned char*.

Что вы можете сделать, это либо изменить аргумент вашей функции:

int I2C1_Write(char DeviceAddress, unsigned char SubAddress, const unsigned char *Payload, char ByteCnt)

или приведите ваш проходной аргумент:

I2C1_Write(SHT31_Address, 0xFF, (unsigned char*)&CMD_SingleShot[1], CMD_SingleShot[0])

В последнем случае помните, что отбрасывание константности может привести к неопределенное поведение при последующем изменении.

...