Подтвердить что ты не робот

Странные результаты для условного оператора с GCC и указателями bool

В следующем коде я memset() a stdbool.h bool значение переменной 123. (Возможно, это поведение undefined?) Затем я передаю указатель на эту переменную функции-жертвы, которая пытается защитить от неожиданных значений с помощью условной операции. Однако GCC по какой-то причине, по-видимому, вообще исключает условную операцию.

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

void victim(bool* foo)
{
    int bar = *foo ? 1 : 0;
    printf("%d\n", bar);
}

int main()
{
    bool x;
    bool *foo = &x;
    memset(foo, 123, sizeof(bool));
    victim(foo);
    return 0;
}
[email protected]:~$ gcc -Wall -O0 test.c
[email protected]:~$ ./a.out 
123

Что особенно неприятно, так это то, что функция victim() фактически находится внутри библиотеки и сработает, если значение больше 1.

Воспроизводится в версиях GCC 4.8.2-19ubuntu1 и 4.7.2-5. Не воспроизводится на clang.

4b9b3361

Ответ 1

(Возможно, это поведение undefined?)

Не напрямую, но чтение с объекта после этого.

Цитата C99:

6.2.6 Представления типов

6.2.6.1 Общие сведения

5 Некоторые представления объектов не обязательно должны представлять значение типа объекта. Если сохраненный значение объекта имеет такое представление и считывается выражением lvalue, которое делает не имеют характера, поведение undefined. [...]

В основном, это означает, что если конкретная реализация решила, что единственными двумя действительными байтами для bool являются 0 и 1, то вам лучше убедиться, что вы не используете никаких обманов чтобы попытаться установить его на любое другое значение.

Ответ 2

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

movzbl (%rax), %eax
movzbl %al, %eax
movl %eax, -4(%rbp)

который выполняет следующие действия:

  • Скопируйте 32 бита из *foo (обозначенный (%rax) в сборке) в регистр %eax и заполните старшие разряды %eax нулями (не так, чтобы их было, потому что %eax является 32-разрядным регистром).
  • Скопируйте 8 битов младшего порядка %eax (обозначается символом %al) на %eax и залейте старшие разряды %eax нулями. Как программист на C, вы понимаете это как %eax &= 0xff.
  • Скопируйте значение %eax в 4 байта выше %rbp, которое является местоположением bar в стеке.

Таким образом, этот код является ассемблерным переводом

int bar = *foo & 0xff;

Ясно, что GCC оптимизировал линию, основанную на том, что a bool никогда не должно содержать значения, отличные от 0 или 1.

Если вы измените соответствующую строку в источнике C на этот

int bar = *((int*)foo) ? 1 : 0;

тогда сборка изменится на

movl (%rax), %eax
testl %eax, %eax
setne %al
movzbl %al, %eax
movl %eax, -4(%rbp)

который выполняет следующие действия:

  • Скопировать 32 бита из *foo (обозначается (%rax) в сборке) в регистр %eax.
  • Протестируйте 32 бита %eax против себя, что означает ANDing его с собой и установки некоторых флагов в процессоре на основе результата. (Здесь AND здесь нет необходимости, но нет инструкции просто проверять регистр и устанавливать флаги.)
  • Задайте 8 разрядов младшего порядка %eax (обозначается символом %al) равным 1, если результат ANDing равен 0 или 0 в противном случае.
  • Скопируйте 8 битов младшего порядка %eax (обозначается символом %al) на %eax и запишем младшие разряды %eax нулями, как в первом фрагменте.
  • Скопируйте значение %eax в 4 байта выше %rbp, которое является местоположением bar в стеке; также как и в первом фрагменте.

Это действительно верный перевод кода на C. И действительно, если вы добавите приведение в (int*) и скомпилируете и запустите программу, вы увидите, что она выводит 1.

Ответ 3

Сохранение значения, отличного от 0 или 1 в bool, - это поведение undefined в C.

Итак, на самом деле это:

int bar = *foo ? 1 : 0;

оптимизирован с чем-то близким к этому:

int bar = *foo ? *foo : 0;