Turgenev 1 15 июля Опубликовано 15 июля (изменено) · Жалоба Работаю с примером Speex AN2812 Keil STM32 (есть у меня в профиле в темах). Возник вопрос когда массиву, объявленному как: char input_bytes[ENCODED_FRAME_SIZE]; начали присваивать поэлементно значения массива, объявленного как: const uint8_t male_voice[] Следующим образом: for(uint8_t i=0;i<ENCODED_FRAME_SIZE-1; i++) { input_bytes[i] = male_voice[sample_index++]; } Почти все ссылки в инете (кроме вики) пишут, что char знаковый, то есть тоже самое что и signed char или int8_t. А так как uint8_t это переопределение типа unsigned char, то при присвоении переменной типа char значения типа unsigned char в переменной char должно получиться значение равное (unsigned char) mod 2^n. Но этого не происходит, присваивается нормально, без потери точности (перепроверил ниже в приведенном коде). И вроде вопросов быть не должно, но если написать условие типа: char char_var = 0; unsigned char uschar_var = 0; signed char schar_var = 0; uschar_var = 251; char_var = uschar_var; schar_var = uschar_var; if (char_var == 245) { char_var = uschar_var; } То компилятор выдаст предупреждение, что условие всегда ложно: "warning: comparison of constant 245 with expression of type 'char' is always false". Значит нельзя так просто присваивать знаковому типу значение беззнакового. А как правильно или как это работает? Просто это все же пример от ST, уровень как никак. Изменено 15 июля пользователем Turgenev Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 15 июля Опубликовано 15 июля · Жалоба 3 минуты назад, Turgenev сказал: Почти все ссылки в инете (кроме вики) пишут, что char знаковый Эти ссылки пишут неправильно. char - это отдельный тип, который может быть как знаковым, так и беззнаковым в зависимости от ключей командной строки при вызове компилятора. Поэтому в правильно написанной программе он должен использоваться исключительно для хранения символов. Для чисел должен использоваться signed char или unsigned char (а лучше - их псевдонимы из stdint.h - int8_t и uint8_t). Поэтому ваш пример написан криворуким программистом и массив input_bytes должен быть объявлен как uint8_t input_bytes[ENCODED_FRAME_SIZE] . 2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Turgenev 1 15 июля Опубликовано 15 июля · Жалоба 1 минуту назад, Сергей Борщ сказал: Поэтому ваш пример написан криворуким программистом и массив input_bytes должен быть объявлен как Думал так избавиться от неопределенности у себя в голове, но там функции чтения/записи кодированных/декодированных данных тоже ждут данные типа char в параметрах, а это уже так просто не заменишь EXPORT int speex_bits_write(SpeexBits *bits, char *chars, int max_nbytes); EXPORT void speex_bits_read_from(SpeexBits *bits, char *chars, int len); Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
_pv 75 15 июля Опубликовано 15 июля · Жалоба для компилятора в данном случае '245' - int и в char физически не влазит, для сравнения. если физически битики 11110101 поместить в uint8_t это будет 245, а если в int8_t, то -11, что совсем не равно 245, о чем компилятор и сообщает. и если привести сравнение к одному соответствующему типу, (int8_t)245, сделав из неё -11, или наоборот (uint8_t)char_var, компилятор спокойно сравнит одинаковые наборы битов 11110101, без разницы "знаковые" они или нет. 2 minutes ago, Сергей Борщ said: char - это отдельный тип, который может быть как знаковым, так и беззнаковым +1, он к тому же ещё на некоторых платформах не всегда 8 бит бывает. 11 minutes ago, Turgenev said: а это уже так просто не заменишь указатель это лишь всего лишь адрес, (void*) chars Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 234 15 июля Опубликовано 15 июля · Жалоба 41 минуту назад, Turgenev сказал: Почти все ссылки в инете Это примерно то же самое, что ОБС (одна бабка сказала). 41 минуту назад, Turgenev сказал: А как правильно или как это работает? Про тип char выше уже сказал Сергей Борщ. Кроме того, вот это: 41 минуту назад, Turgenev сказал: for(uint8_t i=0;i<ENCODED_FRAME_SIZE-1; i++) { input_bytes[i] = male_voice[sample_index++]; } Написано или для 8-битника или криворуким программером также потому, что для переменной цикла на 32-битном МК используется почему-то 8-разрядный тип данных. Грамотный программер для 32-битной системы для переменной цикла в данном случае использует тип int или unsigned или auto (если компилятор его поддерживает). Да и вообще - присвоение скорее всего начнёт с конца. Так как - оптимальнее. 23 минуты назад, Turgenev сказал: Думал так избавиться от неопределенности у себя в голове, но там функции чтения/записи кодированных/декодированных данных тоже ждут данные типа char в параметрах, а это уже так просто не заменишь EXPORT int speex_bits_write(SpeexBits *bits, char *chars, int max_nbytes); EXPORT void speex_bits_read_from(SpeexBits *bits, char *chars, int len); Лучше так: EXPORT int speex_bits_write(SpeexBits *bits, void *chars, int max_nbytes); EXPORT void speex_bits_read_from(SpeexBits *bits, void *chars, int len); И скорее всего в первой функции ещё и void const *chars (там же *chars используется явно только на чтение?). С приведением типа указателя в теле функции. Имхо: Входные аргументы-указатели на массивы данных, которые функцией используются не как конкретно данные данного типа, а как массивы байт, лучше всегда делать типа void * (или void const *). Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Turgenev 1 15 июля Опубликовано 15 июля · Жалоба 23 минуты назад, jcxz сказал: И скорее всего в первой функции ещё и void const *chars (там же *chars используется явно только на чтение?). С приведением типа указателя в теле функции. Да, вроде только на чтение. Реализацию функций из оригинала прилагаю: Спойлер EXPORT void speex_bits_read_from(SpeexBits *bits, char *chars, int len) { int i; int nchars = len / BYTES_PER_CHAR; if (nchars > bits->buf_size) { speex_notify("Packet is larger than allocated buffer"); if (bits->owner) { char *tmp = (char*)speex_realloc(bits->chars, nchars); if (tmp) { bits->buf_size=nchars; bits->chars=tmp; } else { nchars=bits->buf_size; speex_warning("Could not resize input buffer: truncating input"); } } else { speex_warning("Do not own input buffer: truncating oversize input"); nchars=bits->buf_size; } } #if (BYTES_PER_CHAR==2) /* Swap bytes to proper endian order (could be done externally) */ #define HTOLS(A) ((((A) >> 8)&0xff)|(((A) & 0xff)<<8)) #else #define HTOLS(A) (A) #endif for (i=0;i<nchars;i++) bits->chars[i]=HTOLS(chars[i]); bits->nbBits=nchars<<LOG2_BITS_PER_CHAR; bits->charPtr=0; bits->bitPtr=0; bits->overflow=0; } EXPORT int speex_bits_write(SpeexBits *bits, char *chars, int max_nbytes) { int i; int max_nchars = max_nbytes/BYTES_PER_CHAR; int charPtr, bitPtr, nbBits; /* Insert terminator, but save the data so we can put it back after */ bitPtr=bits->bitPtr; charPtr=bits->charPtr; nbBits=bits->nbBits; speex_bits_insert_terminator(bits); bits->bitPtr=bitPtr; bits->charPtr=charPtr; bits->nbBits=nbBits; if (max_nchars > ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR)) max_nchars = ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); for (i=0;i<max_nchars;i++) chars[i]=HTOLS(bits->chars[i]); return max_nchars*BYTES_PER_CHAR; } 26 минут назад, jcxz сказал: Имхо: Входные аргументы-указатели на массивы данных, которые функцией используются не как конкретно данные данного типа, а как массивы байт, лучше всегда делать типа void * (или void const *). А вот, кстати, по поводу типа void в параметрах функции. Есть функция: Спойлер RINGBUF_STATUS RingBuf_DataWatch(void *data, u16_t len, RINGBUF_t *rb); И ее реализация: Спойлер /** * @brief Watch current data in the buf * @note Reads data without shifting in the buffer * * @param[out] data Data from buffer * @param[in] len Length of data to be read [bytes] * @param[in] rb #RINGBUF_t structure instance * @return #RINGBUF_STATUS enum */ RINGBUF_STATUS RingBuf_DataWatch(void *data, u16_t len, RINGBUF_t *rb) { if (data == NULL) return RINGBUF_PARAM_ERR; if (len > rb->size) return RINGBUF_OVERFLOW; // OUTPUT data index start address u16_t s_addr = 0; // available space in the end of buffer u16_t space = rb->size - rb->tail; u16_t loc_tail = rb->tail; if (len > space) { // if len > available space // recast pointer to u8_t // copy data from available space memcpy((&data[s_addr * rb->cell_size]), &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); // next reading will start from 0 loc_tail = 0; // new start address - space length s_addr = space; // new length - len-space len -= space; } // copy all the data from the buf storage memcpy(&data[s_addr * rb->cell_size], &rb->buf[loc_tail * rb->cell_size], len * rb->cell_size); return RINGBUF_OK; } Тип параметра как вы и пишите- void. Но на строчках: ... memcpy((&data[s_addr * rb->cell_size]), &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); ... memcpy(&data[s_addr * rb->cell_size], &rb->buf[loc_tail * rb->cell_size], len * rb->cell_size); ... компилятор выдаст ошибку: error: #852: expression must be a pointer to a complete object type. Потому что компилятор не знает по сколько ему прибавлять ячеек памяти. Я пролечил указанием в параметрах функции типа uint8_t вместо void, тем самым ограничив работу функции (а в целом и всей библиотеки) только значениями типа uint8_t. А как такое лечить, чтобы не ограничивать возможности функции? Функция взята из гитхаба 41 минуту назад, jcxz сказал: Входные аргументы-указатели на массивы данных, которые функцией используются не как конкретно данные данного типа, а как массивы байт, лучше всегда делать типа void * (или void const *). Но тут тоже есть нюанс. Например, функция из библиотеки с гитхаба: Спойлер RINGBUF_STATUS RingBuf_DataWatch(void *data, u16_t len, RINGBUF_t *rb); И ее реализация: Спойлер /** * @brief Watch current data in the buf * @note Reads data without shifting in the buffer * * @param[out] data Data from buffer * @param[in] len Length of data to be read [bytes] * @param[in] rb #RINGBUF_t structure instance * @return #RINGBUF_STATUS enum */ RINGBUF_STATUS RingBuf_DataWatch(void *data, u16_t len, RINGBUF_t *rb) { if (data == NULL) return RINGBUF_PARAM_ERR; if (len > rb->size) return RINGBUF_OVERFLOW; // OUTPUT data index start address u16_t s_addr = 0; // available space in the end of buffer u16_t space = rb->size - rb->tail; u16_t loc_tail = rb->tail; if (len > space) { // if len > available space // recast pointer to u8_t // copy data from available space memcpy((&data[s_addr * rb->cell_size]), &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); // next reading will start from 0 loc_tail = 0; // new start address - space length s_addr = space; // new length - len-space len -= space; } // copy all the data from the buf storage memcpy(&data[s_addr * rb->cell_size], &rb->buf[loc_tail * rb->cell_size], len * rb->cell_size); return RINGBUF_OK; } В параметрах функции тип принимаемых данных void *, как вы и указали. Но на строчках: ... memcpy((&data[s_addr * rb->cell_size]), &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); ... memcpy(&data[s_addr * rb->cell_size], &rb->buf[loc_tail * rb->cell_size], len * rb->cell_size); ... Компилятор выдаст ошибку: error: #852: expression must be a pointer to a complete object type. Потому что компилятор не знает по сколько ячеек памяти прибавлять, так как тип данных не известен. Я пролечил проблему заменой типа данных с void на Uint8_t, но и возможности функции тем самым ограничил. А как тут решался бы конфликт из-за типа void и указателя на него без ограничения возможностей функции? В параметрах функции тип принимаемых данных void *, как вы и указали. Но на строчках: ... memcpy((&data[s_addr * rb->cell_size]), &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); ... memcpy(&data[s_addr * rb->cell_size], &rb->buf[loc_tail * rb->cell_size], len * rb->cell_size); ... Компилятор выдаст ошибку: error: #852: expression must be a pointer to a complete object type. Потому что компилятор не знает по сколько ячеек памяти прибавлять, так как тип данных не известен. Я пролечил проблему заменой типа данных с void на Uint8_t, но и возможности функции тем самым ограничил. А как тут решался бы конфликт из-за типа void и указателя на него без ограничения возможностей функции? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 234 15 июля Опубликовано 15 июля · Жалоба 1 час назад, Turgenev сказал: В параметрах функции тип принимаемых данных void *, как вы и указали. Но на строчках: ... memcpy((&data[s_addr * rb->cell_size]), &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); ... Написано опять криворуким. void * - указатель на безразмерный тип, а значит к нему нельзя применять операции индексирования ([]), разъименования (*) и подобные. Сперва его нужно привести к нужному размерному типу. О чём я выше писал: 1 час назад, jcxz сказал: С приведением типа указателя в теле функции. 1 час назад, Turgenev сказал: А как тут решался бы конфликт из-за типа void и указателя на него без ограничения возможностей функции? Что мешает в нужном конкретном месте сделать приведение типа data? memcpy((u8 *)data + s_addr * rb->cell_size, &rb->buf[loc_tail * rb->cell_size], space * rb->cell_size); Здесь вы приказываете компилятору рассматривать data - как указатель на байты (u8 *). 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться