Приведение C++ к байту (unit8_t) во время вычитания не вызовет недостаточного заполнения, как я ожидал; вывод int16_t; Зачем?

Обратите внимание, что byte это 8-битный тип (uint8_t), а unsigned int это 16-битный тип (uint16_t).

Следующее не дает ожидаемых результатов. Я ожидаю, что он опустится, и результатом всегда будет uint8_t, но вместо этого он становится int со знаком (int16_t)!!! Зачем?

В частности, обратите внимание на следующую строку кода: (byte)seconds - tStart Я ожидаю, что его вывод ВСЕГДА будет 8-разрядным значением без знака (uint8_t), но вместо этого он выводит 16-разрядное значение со знаком: int16_t.

Как я могу получить результат вычитания, чтобы всегда быть типом uint8_t?

while (true)
{
  static byte tStart = 0;
  static unsigned int seconds = 0;
  seconds++;

  //Print output from microcontroller
  typeNum((byte)seconds); typeString(", "); typeNum(tStart); typeString(", "); 
  typeNum((byte)seconds - tStart); typeString("\n");

  if ((byte)seconds - tStart >= (byte)15)
  {
    typeString("TRUE!\n");
    tStart = seconds; //update 
  }
}

Образец вывода:

Колонка 1 является (byte)seconds Столбец 2 tStart Столбец 3 - столбец 1 минус столбец 2 ((byte)seconds - tStart Обратите внимание, что столбец 3 становится отрицательным (int8_t) после переполнения столбца 1 с 255 до 0. Я ожидаю (и хочу), чтобы он оставался положительным (беззнаковым) 8-разрядным значением из-за недостаточного значения.

196, 195, 1
197, 195, 2
198, 195, 3
199, 195, 4
200, 195, 5
201, 195, 6
202, 195, 7
203, 195, 8
204, 195, 9
205, 195, 10
206, 195, 11
207, 195, 12
208, 195, 13
209, 195, 14
210, 195, 15
TRUE!
211, 210, 1
212, 210, 2
213, 210, 3
214, 210, 4
215, 210, 5
216, 210, 6
217, 210, 7
218, 210, 8
219, 210, 9
220, 210, 10
221, 210, 11
222, 210, 12
223, 210, 13
224, 210, 14
225, 210, 15
TRUE!
226, 225, 1
227, 225, 2
228, 225, 3
229, 225, 4
230, 225, 5
231, 225, 6
232, 225, 7
233, 225, 8
234, 225, 9
235, 225, 10
236, 225, 11
237, 225, 12
238, 225, 13
239, 225, 14
240, 225, 15
TRUE!
241, 240, 1
242, 240, 2
243, 240, 3
244, 240, 4
245, 240, 5
246, 240, 6
247, 240, 7
248, 240, 8
249, 240, 9
250, 240, 10
251, 240, 11
252, 240, 12
253, 240, 13
254, 240, 14
255, 240, 15
TRUE!
0, 255, -255
1, 255, -254
2, 255, -253
3, 255, -252
4, 255, -251
5, 255, -250
6, 255, -249
7, 255, -248
8, 255, -247
9, 255, -246
10, 255, -245
11, 255, -244
12, 255, -243
13, 255, -242
14, 255, -241
15, 255, -240
16, 255, -239
17, 255, -238
18, 255, -237
19, 255, -236
20, 255, -235
21, 255, -234
22, 255, -233
23, 255, -232
24, 255, -231
25, 255, -230
26, 255, -229
27, 255, -228
28, 255, -227
29, 255, -226
30, 255, -225
31, 255, -224
32, 255, -223
33, 255, -222
34, 255, -221
35, 255, -220

Здесь typeNum функция сверху:

//--------------------------------------------------------------------------------------------
//typeNum (overloaded)
//-see AVRLibC int to string functions: http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html
//--------------------------------------------------------------------------------------------
//UNSIGNED:
void typeNum(uint8_t myNum)
{
  char buffer[4]; //3 for the number (up to 2^8 - 1, or 255 max), plus 1 char for the null terminator 
  utoa(myNum, buffer, 10); //base 10 number system 
  typeString(buffer);
}
void typeNum(uint16_t myNum)
{
  char buffer[6]; //5 for the number (up to 2^16 - 1, or 65535 max), plus 1 char for the null terminator 
  utoa(myNum, buffer, 10); //base 10 number system 
  typeString(buffer);
}
void typeNum(uint32_t myNum)
{
  char buffer[11]; //10 chars for the number (up to 2^32 - 1, or 4294967295 max), plus 1 char for the null terminator 
  ultoa(myNum, buffer, 10); //base 10 number system 
  typeString(buffer);
}

//SIGNED:
void typeNum(int8_t myNum)
{
  char buffer[5]; //4 for the number (down to -128), plus 1 char for the null terminator 
  itoa(myNum, buffer, 10); //base 10 number system 
  typeString(buffer);
}
void typeNum(int16_t myNum)
{
  char buffer[7]; //6 for the number (down to -32768), plus 1 char for the null terminator 
  itoa(myNum, buffer, 10); //base 10 number system 
  typeString(buffer);
}
void typeNum(int32_t myNum)
{
  char buffer[12]; //11 chars for the number (down to -2147483648), plus 1 char for the null terminator 
  ltoa(myNum, buffer, 10); //base 10 number system 
  typeString(buffer);
}

1 ответ

Решение

Итак, я понял это:

Ответ очень прост, но понимание этого не является.

Ответ:

(Как это исправить):
Вместо того, чтобы использовать (byte)seconds - tStart использовать (byte)((byte)seconds - tStart), Это оно! Задача решена! Все, что вам нужно сделать, это привести результат математической операции (в данном случае вычитание) к byte а также, и это исправлено! В противном случае он возвращается как подписанное int, что приводит к ошибочному поведению.

Итак, почему это происходит?

Ответ:
В C, C++ и C# нет такой вещи, как математическая операция с байтом! По-видимому, функции уровня сборки, необходимые для таких операторов, как +, - и т. Д., Не существуют для байтовых вводов. Вместо этого все байты сначала неявно преобразуются (преобразуются) в int перед выполнением операции, затем математическая операция выполняется над целыми числами, и когда она завершается, она также возвращает int!

Итак, этот код (byte)seconds - tStart неявно приводится (повышается в этом случае) компилятором следующим образом: (int)(byte)seconds - (int)tStart... и возвращает int тоже. Смущает, а? Я конечно так думал!

Вот еще немного чтения по этому вопросу:

(чем больше звездочек *, тем полезнее)

Теперь давайте посмотрим на некоторые реальные примеры C++:

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

#include <iostream>

using namespace std;

//----------------------------------------------------------------
//printTypeAndVal (overloaded function)
//----------------------------------------------------------------
//UNSIGNED:
void printTypeAndVal(uint8_t myVal)
{
  cout << "uint8_t = " << (int)myVal << endl; //(int) cast is required to prevent myVal from printing as a char
}
void printTypeAndVal(uint16_t myVal)
{
  cout << "uint16_t = " << myVal << endl;
}
void printTypeAndVal(uint32_t myVal)
{
  cout << "uint32_t = " << myVal << endl;
}
void printTypeAndVal(uint64_t myVal)
{
  cout << "uint64_t = " << myVal << endl;
}
//SIGNED:
void printTypeAndVal(int8_t myVal)
{
  cout << "int8_t = " << (int)myVal << endl; //(int) cast is required to prevent myVal from printing as a char
}
void printTypeAndVal(int16_t myVal)
{
  cout << "int16_t = " << myVal << endl;
}
void printTypeAndVal(int32_t myVal)
{
  cout << "int32_t = " << myVal << endl;
}
void printTypeAndVal(int64_t myVal)
{
  cout << "int64_t = " << myVal << endl;
}
//FLOATING TYPES:
void printTypeAndVal(float myVal)
{
  cout << "float = " << myVal << endl;
}
void printTypeAndVal(double myVal)
{
  cout << "double = " << myVal << endl;
}
void printTypeAndVal(long double myVal)
{
  cout << "long double = " << myVal << endl;
}

//----------------------------------------------------------------
//main
//----------------------------------------------------------------
int main()
{
  cout << "Begin\n\n";

  //Test variables
  uint8_t u1 = 0;
  uint8_t u2 = 1;

  //Test cases:

  //for a single byte, explicit cast of the OUTPUT from the mathematical operation is required to get desired *unsigned* output
  cout << "uint8_t - uint8_t:" << endl;
  printTypeAndVal(u1 - u2); //-1 (bad)
  printTypeAndVal((uint8_t)u1 - (uint8_t)u2); //-1 (bad)
  printTypeAndVal((uint8_t)(u1 - u2)); //255 (fixed!)
  printTypeAndVal((uint8_t)((uint8_t)u1 - (uint8_t)u2)); //255 (fixed!)
  cout << endl;

  //for unsigned 2-byte types, explicit casting of the OUTPUT is required too to get desired *unsigned* output
  cout << "uint16_t - uint16_t:" << endl;
  uint16_t u3 = 0;
  uint16_t u4 = 1;
  printTypeAndVal(u3 - u4); //-1 (bad)
  printTypeAndVal((uint16_t)(u3 - u4)); //65535 (fixed!)
  cout << endl;

  //for larger standard unsigned types, explicit casting of the OUTPUT is ***NOT*** required to get desired *unsigned* output! IN THIS CASE, NO IMPLICIT PROMOTION (CAST) TO A LARGER *SIGNED* TYPE OCCURS.
  cout << "unsigned int - unsigned int:" << endl;
  unsigned int u5 = 0;
  unsigned int u6 = 1;
  printTypeAndVal(u5 - u6); //4294967295 (good--no fixing is required)
  printTypeAndVal((unsigned int)(u5 - u6)); //4294967295 (good--no fixing was required)
  cout << endl;

  return 0;
}

Вы также можете запустить эту программу онлайн здесь: http://cpp.sh/6kjgq

Вот выход. Обратите внимание, что оба байта без знака uint8_t - uint8_t регистр и двойной байт без знака uint16_t - uint16_t каждый случай был неявно приведен (повышен) компилятором C++ к 4-байтовому знаку int32_t (int) тип переменной. Это поведение, которое вы должны заметить. Следовательно, результат этих вычитаний отрицателен, что является необычным поведением, которое изначально смутило меня, поскольку я ожидал, что вместо этого он превратится в недопустимое значение максимальной переменной без знака (поскольку мы делаем 0 - 1). Чтобы достичь желаемого значения недостаточности, мне пришлось явно привести выходной результат вычитания к желаемому типу без знака, а не только к входным данным. Для unsigned int В этом случае явное приведение результата НЕ требовалось.

Начать

uint8_t - uint8_t:
int32_t = -1
int32_t = -1
uint8_t = 255
uint8_t = 255

uint16_t - uint16_t:
int32_t = -1
uint16_t = 65535

unsigned int - unsigned int:
uint32_t = 4294967295
uint32_t = 4294967295

Вот еще один краткий пример программы, чтобы показать, что один байт без знака (unsigned char) переменные преобразуются в подписанные целые числа (int) при операции.

#include <stdio.h>

int main(int argc, char **argv) 
{
  unsigned char x = 130;
  unsigned char y = 130;
  unsigned char z = x + y;

  printf("%u\n", x + y); // Prints 260.
  printf("%u\n", z);     // Prints 4.
}

Выход:

260
4

Тест здесь: http://cpp.sh/84eo

Другие вопросы по тегам