Я давно наткнулся на интересный вопрос на форуме и хочу знать ответ.
Рассмотрим следующую функцию C:
f1.c
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
Это должно всегда возвращаться false
с тех пор var3 == 3000
. main
Функция выглядит следующим образом :
main.c
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
Так как f1()
должен всегда возвращаться false
, можно ожидать, что программа выведет на экран только одну ложь . Но после компиляции и запуска его, выполняются также отображаются:
$ gcc main.c f1.c -o test
$ ./test
false
executed
Это почему? Есть ли в этом коде неопределенное поведение?
Примечание: я скомпилировал это с gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
.
f1()
в тот же файл, чтоmain()
и вы, вы получите некоторую странность: хотя в C ++ правильно использовать()
пустой список параметров, в C это используется для функции с еще не определенным списком параметров ( в основном он ожидает список параметров в стиле K & R после)
). Чтобы быть правильным C, вы должны изменить свой код наbool f1(void)
.main()
было бы упростить доint main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }
- это лучше показало бы несоответствие.void
?true
иfalse
в K & R 1-е изд., поэтому таких проблем не было вообще. Это был просто 0 и ненулевой для истины и ложи. Не так ли? Я не знаю, были ли доступны прототипы в то время._Bool
типа, ни<stdbool.h>
заголовка.Ответы:
Как отмечалось в других ответах, проблема в том, что вы используете
gcc
без установленных опций компилятора. Если вы сделаете это, то по умолчанию будет использоваться то, что называется «gnu90», что является нестандартной реализацией старого, отозванного стандарта C90 1990 года.В старом стандарте C90 существовал большой недостаток языка C: если вы не объявляли прототип перед использованием функции, он по умолчанию был бы
int func ()
(где( )
означает «принять любой параметр»). Это изменяет соглашение о вызове функцииfunc
, но не меняет фактическое определение функции. Поскольку размерbool
иint
отличается, ваш код вызывает неопределенное поведение при вызове функции.Это опасное бессмысленное поведение было исправлено в 1999 году, с выпуском стандарта C99. Неявные объявления функций были запрещены.
К сожалению, GCC до версии 5.xx по-прежнему использует старый стандарт C по умолчанию. Вероятно, нет никаких причин, по которым вам следует компилировать ваш код как что-либо, кроме стандартного C. Поэтому вы должны явно указать GCC, что он должен компилировать ваш код как современный код C, а не как нестандартное дерьмо GNU более 25 лет. ,
Исправьте проблему, всегда компилируя вашу программу как:
-std=c11
говорит ему сделать нерешительную попытку компилирования в соответствии с (текущим) стандартом C (неофициально известным как C11).-pedantic-errors
говорит ему от всего сердца сделать вышеизложенное и выдавать ошибки компилятора, когда вы пишете неправильный код, который нарушает стандарт Си.-Wall
означает дать мне несколько дополнительных предупреждений, которые могут быть полезны.-Wextra
означает дать мне несколько дополнительных предупреждений, которые могут быть полезны.источник
-std=gnu11
гораздо более вероятно, что они будут работать-std=c11
должным образом, чем из-за какой-либо или всех из них: необходимая функциональность библиотеки за пределами C11 (POSIX, X / Open и т. Д.), Доступная в «gnu» расширенные режимы, но подавленные в режиме строгого соответствия; ошибки в системных заголовках, которые скрыты в расширенных режимах, например, при допущении наличия нестандартных typedefs; непреднамеренное использование триграфов (этот стандартный сбой отключен в режиме "gnu").-pedantic-errors
это менее хлопотно, чем то,-Werror
но оба могут и не приводить к тому, что программы не могут компилироваться в операционных системах, которые не включены в первоначальное авторское тестирование, даже когда нет реальной проблемы.bool
/,_Bool
то вы можете написать свой код C "C ++ - esque", где вы предполагаете, что все сравнения и логические операторы возвращаютbool
аналог в C ++, даже если они возвращаютint
в C по историческим причинам. , Это имеет большое преимущество, заключающееся в том, что вы можете использовать инструменты статического анализа для проверки безопасности типов всех таких выражений и выявлять все виды ошибок во время компиляции. Это также способ выразить намерение в виде самодокументируемого кода. И что не менее важно, он также экономит несколько байтов оперативной памяти.У вас нет объявленного прототипа для
f1()
в main.c, поэтому он неявно определяется какint f1()
, то есть это функция, которая принимает неизвестное количество аргументов и возвращает anint
.Если
int
иbool
имеют разные размеры, это приведет к неопределенному поведению . Например, на моей машинеint
это 4 байта иbool
один байт. Поскольку функция определена для возвратаbool
, она один байт в стек при возврате. Однако, поскольку он неявно объявлен для возвратаint
из main.c, вызывающая функция попытается прочитать 4 байта из стека.Параметры компилятора по умолчанию в gcc не скажут вам, что он делает это. Но если вы скомпилируете с
-Wall -Wextra
, вы получите это:Чтобы это исправить, добавьте объявление для
f1
в main.c, передmain
:Обратите внимание, что список аргументов явно установлен на
void
, что говорит компилятору, что функция не принимает аргументов, в отличие от пустого списка параметров, который означает неизвестное количество аргументов. Определениеf1
в f1.c также должно быть изменено, чтобы отразить это.источник
-Werror-implicit-function-declaration
к настройкам GCC, так что этот больше не ускользает. Еще лучший выбор --Werror
превратить все предупреждения в ошибки. Заставляет вас исправить все предупреждения, когда они появляются.Я думаю, что интересно посмотреть, где на самом деле происходит несоответствие размеров, упомянутое в отличном ответе Лундина.
Если вы скомпилируете
--save-temps
, вы получите файлы сборки, которые вы можете посмотреть. Вот часть, гдеf1()
выполняется== 0
сравнение и возвращает его значение:Возвращающая часть есть
sete %al
. В соглашениях о вызовах x86 в C возвращаемые значения размером 4 байта или меньше (включаяint
иbool
) возвращаются через регистр%eax
.%al
самый младший байт%eax
. Итак, старшие 3 байта%eax
остаются в неконтролируемом состоянии.Сейчас в
main()
:Это проверяет , является ли весь из
%eax
равен нулю, потому что он думает , что это тестирование Int.Добавление явного объявления функции изменяется
main()
на:что мы и хотим
источник
Пожалуйста, скомпилируйте с такой командой:
Вывод:
С таким сообщением вы должны знать, что нужно сделать, чтобы исправить это.
Редактировать: после прочтения (теперь удаленного) комментария я попытался скомпилировать ваш код без флагов. Ну, это привело меня к ошибкам компоновщика без предупреждений компилятора вместо ошибок компилятора. И эти ошибки компоновщика труднее понять, поэтому, даже если в
-std-gnu99
этом нет необходимости, попробуйте всегда использовать их, по крайней мере,-Wall -Werror
это избавит вас от боли в заднице.источник