Предупреждение о преобразовании типа после побитовых операций в C

Как вы объясните, что строка 7 получает предупреждение, но не строка 5 или строка 6?

int main()
{
    unsigned char a = 0xFF;
    unsigned char b = 0xFF;
    a = a | b;                        // 5: (no warning)
    a = (unsigned char)(b & 0xF);     // 6: (no warning)
    a = a | (unsigned char)(b & 0xF); // 7: (warning)
    return 0;
}

Выход GCC 4.6.2 при компиляции в 32-разрядной архитектуре (ПК с ОС Windows):

gcc -c main.c --std=c89 -Wall -Wextra -Wconversion -pedantic
main.c: In function 'main':
main.c:7:11: warning: conversion to 'unsigned char' from 'int' may alter its value [-Wconversion]

Если это поможет вам понять мой вопрос, вот как я вижу это (возможно, неверно!):

Я полагаю, что на 32-битной машине операции выполняются на 32-битных номерах. Поскольку unsigned char вписывается в 32-разрядный int, результат операции 32-бит int. Но поскольку GCC не дает предупреждений на строках 5 и 6, я думаю, что происходит что-то еще:

строка 5: Показатели GCC, которые (uchar) ИЛИ (uchar) никогда не больше MAX (uchar), поэтому никаких предупреждений.

строка 6: Показатели GCC, которые (uchar) и 0xF никогда не больше MAX (uchar), поэтому никаких предупреждений. Явный приведение не требуется.

строка 7: Основываясь на вышеприведенных предположениях: И не должен давать предупреждения (начиная с строки 6), ИЛИ не должен давать предупреждения (начиная с строки 5).

Я предполагаю, что моя логика там где-то виновата. Помогите мне понять логику компилятора.

Ответ 1

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

Итак, я считаю (внимание), что инженеры-компиляторы будут идти следующим образом:

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

Я ожидал бы, что люди напишут код, в котором либо результат будет отправлен на (unsigned char), либо где внешний оператор маскирует все более высокие байты с константой.

  • a = (unsigned char) ( /* some obscure bit-wise expressoin */ ); будет в порядке, а затем
  • a = 0xff & ( /* some obscure bit-wise expressoin */ ); также ОК

если вы знаете, что ваш компилятор правильно переводит эти два шаблона, другие случаи не должны вас слишком беспокоить.

Я видел компиляторы, которые выдавали бы предупреждение из-за a = a | b;, поэтому GCC, не дающий предупреждения, является бесплатным бонусом. может быть, что gcc просто передает постоянное назначение в a | b и, следовательно, заменяет его на 0xff | 0xff, который, как известно, работает без проблем. Если это произойдет, хотя я не знаю, почему он не может получить постоянное значение a в других операторах.

Ответ 2

Я использую linux x86_64, GCC 4.70. И получите ту же ошибку. Я компилирую код и использую gdb, чтобы разобрать исполняемый файл. Вот что я получаю.

(gdb) l
1   int main(){
2     unsigned char a = 0xff;
3     unsigned char b = 0xff;
4     a = a | b;
5     a = (unsigned char)(b & 0xf);
6     a |= (unsigned char)(b & 0xf); 
7     return 0;
8   }
(gdb) b 4
Breakpoint 1 at 0x4004a8: file test.c, line 4.
(gdb) b 5
Breakpoint 2 at 0x4004af: file test.c, line 5.
(gdb) b 6
Breakpoint 3 at 0x4004b9: file test.c, line 6.
(gdb) r
Starting program: /home/spyder/stackoverflow/a.out 

Breakpoint 1, main () at test.c:4
4     a = a | b;
(gdb) disassemble 
Dump of assembler code for function main:
   0x000000000040049c <+0>: push   %rbp
   0x000000000040049d <+1>: mov    %rsp,%rbp
   0x00000000004004a0 <+4>: movb   $0xff,-0x1(%rbp)
   0x00000000004004a4 <+8>: movb   $0xff,-0x2(%rbp)
=> 0x00000000004004a8 <+12>:    movzbl -0x2(%rbp),%eax
   0x00000000004004ac <+16>:    or     %al,-0x1(%rbp)
   0x00000000004004af <+19>:    movzbl -0x2(%rbp),%eax
   0x00000000004004b3 <+23>:    and    $0xf,%eax
   0x00000000004004b6 <+26>:    mov    %al,-0x1(%rbp)
   0x00000000004004b9 <+29>:    movzbl -0x2(%rbp),%eax
   0x00000000004004bd <+33>:    mov    %eax,%edx
   0x00000000004004bf <+35>:    and    $0xf,%edx
   0x00000000004004c2 <+38>:    movzbl -0x1(%rbp),%eax
   0x00000000004004c6 <+42>:    or     %edx,%eax
   0x00000000004004c8 <+44>:    mov    %al,-0x1(%rbp)
   0x00000000004004cb <+47>:    mov    $0x0,%eax
   0x00000000004004d0 <+52>:    pop    %rbp
   0x00000000004004d1 <+53>:    retq   
End of assembler dump.

a = a | b скомпилирован в

movzbl -0x2(%rbp),%eax
or     %al,-0x1(%rbp)

a = (unsigned char)(b & 0xf) скомпилирован в

mov    %al,-0x2(%rbp)
and    $0xf,%eax
mov    %al,-0x1(%rbp)

a |= (unsigned char)(b & 0xf); скомпилирован в

movzbl -0x2(%rbp),%eax
mov    %eax,%edx
and    $0xf,%edx
movzbl -0x1(%rbp),%eax
or     %edx,%eax
mov    %al,-0x1(%rbp)

экспликация не появилась в коде asm. Проблема в том, когда выполняется операция (b и 0xf). вывод операции sizeof(int). Поэтому вы должны использовать это вместо:

a = (unsigned char)(a | (b & 0xF));

PS: explict cast не генерирует никаких предупреждений. даже вы что-то потеряете.

Ответ 3

Я думаю, что проблема заключается в том, что вы конвертируете int в unsigned char и обратно в int.

Линия 6 преобразует int в unsigned char, но просто сохраняет ее в unsigned char.
Строка 7 преобразует int в unsigned char, а затем, чтобы выполнить арифметику, преобразует ее обратно в int. Новое целое число может отличаться от оригинала, поэтому вы получаете предупреждение.

Ответ 4

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

Таким образом, это не связано с побитовым оператором, оно связано с типизацией из переменной 4 байта в 1 байта.