Различия в синтаксисе операторов if между C и C++

if (1) int a = 2;

Эта строка кода является действительным кодом C++ (он компилируется как минимум), но недействительным кодом C (не компилируется). Я знаю, что между языками есть различия, но этот был неожиданным.

Я всегда думал, что грамматика

if (expr) statement

но это сделает его действительным в обоих случаях.

Мои вопросы:

  1. Почему это не компилируется в C?
  2. Почему существует эта разница?

Какой компилятор? В чем ошибка?

lakeweb 03.07.2022 01:19

Не могли бы вы прислать, какую ошибку вы получаете при компиляции, и, как сказал Lakeweb, какой компилятор вы используете

Saurav Chittal 03.07.2022 01:22
почему существует эта разница? Я бы ответил: потому что C и C++ — разные языки программирования.
273K 03.07.2022 01:27

См. ответ Болова ниже для более значимой причины, почему существует разница.

Alexander Guyer 03.07.2022 01:35

Как указано в ответах ниже, это происходит всякий раз, когда ожидается утверждение, поэтому не ограничивается if. Это включает в себя «строку после» for, while, switch и ярлыки, и это лишь некоторые из них.

YiFei 03.07.2022 01:48

Еще одно место, которое показывает разницу, заключается в том, что вы не можете добавить метку к объявлению в C.

Jonathan Leffler 03.07.2022 04:51

@JonathanLeffler, только что попробовал, и, кажется, я могу добавить метку или регистр перед объявлением в C99 и C11.

Klaas van Aarsen 04.07.2022 14:57

@KlaasvanAarsen: тестирование, особенно с GCC, неинформативно. §6.8.1 Помеченные операторы указывает, что помечать можно только операторы, а §6.8.2 Составной оператор указывает, что блочные операторы состоят из последовательностей объявлений и операторов. Грамматика стандарта C не допускает меток в объявлениях. С++ разрешает их; некоторые компиляторы C могут разрешить это, но это расширение по сравнению с (текущим) стандартным C. C2x может это изменить.

Jonathan Leffler 04.07.2022 15:05
if (1) int a = 2; это просто объявляет и инициализирует переменную, которая сразу выходит за рамки?
OmarL 06.07.2022 10:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
93
9
7 874
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

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

Из грамматики C (спецификация C17):

compound-statement: "{" block-item-listopt "}"
block-item-list: block-item | block-item-listblock-item
block-item: declaration | statement

Из грамматики С++ (спецификация С++ 14):

compound-statement: "{" statement-seqopt "}"
statement-seq: statement | statement-seqstatement
statement: ... | declaration-statement | ...

Неясно, Почему существует ли эта разница, просто языки эволюционировали. Синтаксис C++ восходит к (как минимум) C++85. Синтаксис C был введен где-то между C89 и C99 (в C89 объявления должны были находиться в начале блока).


В исходных версиях C++ 85 и 89 область действия переменной, определенной в заявление-декларация, была "до конца объемлющего блокировать"). Таким образом, объявление в подобном if не сразу выйдет за пределы области действия (как это происходит в более поздних версиях), а вместо этого будет в области действия для операторов, следующих за if в той же области действия блока. Это может привести к проблемам с доступом к неинициализированным данным, когда условие ложно. Хуже того, если бы у var был нетривиальный деструктор, который вызывался бы, когда область видимости заканчивалась, даже если бы она никогда не была инициализирована! Я подозреваю, что попытка избежать подобных проблем привела к тому, что C принял другой синтаксис.

Imho, переход C на определение C++ упростил бы язык, удалив ошибки, в дополнение к удалению случайных различий. Просто позор, что они не сделали этого в C99, когда следовали общепринятой практике и позволяли смешивать утверждения и определения.

Deduplicator 03.07.2022 22:57

Это потому, что C и C++ по-разному определяют утверждение.

В C объявления не классифицируются как операторы. Составной оператор C состоит из открывающей {, списка опций блок-элемент и закрывающей }, где блок-элемент либо декларация, либо утверждение. (Это было изменено в C99, когда C добавил возможность смешивать объявления и операторы внутри блока.)

В C++ объявление классифицируется как оператор (но только если оно находится внутри составного оператора). Это позволяет упростить определение составное выражение: это {, за которым следует необязательный список утверждение, за которым следует }.

Разница не имеет большого практического эффекта; это всегда можно обойти. Одним из последствий является то, что в C++ допустимо, чтобы объявление сразу же следовало за меткой case, но в C это недопустимо.

В C декларация и утверждение являются разными объектами.

В C++ подмножество объявлений, называемое объявление блока, представляет собой тип оператора, в частности, это заявление-декларация. К ним относятся простые объявления, такие как int a=2.

ответ Криса (и другие) показывает, чем отличается грамматика.

Я хочу отметить, что if (1) int a = 2; не имеет смысла в C, но имеет смысл в C++. Поскольку у нас нет блока, а есть только 1 оператор/объявление, невозможно дальнейшее использование объявленной переменной (она сразу же выходит из области видимости). В C это не имеет смысла разрешать, но в C++ конструкторы и деструкторы могут иметь побочные эффекты, поэтому определение и инициализация переменной, которая немедленно выходит за пределы области видимости, может быть полезной и должна быть разрешена.

Однако адрес можно было использовать. int a=foo(&a);.

tstanisl 03.07.2022 08:52

@tstanisl Но каков для этого возможный вариант использования? Все, о чем я могу думать, это если foo() требуется указатель на выходное местоположение, и вам не нужен этот результат.

Barmar 03.07.2022 21:45

@Barmar, да, но foo() может иметь некоторые побочные эффекты, и для выполнения этой работы может потребоваться внешнее хранилище. Хотя использование составного литерала foo(&(int){0}) может быть более подходящим решением.

tstanisl 03.07.2022 22:26

Это одинаково полезно / бесполезно на любом языке, законно только на одном языке - это просто случайное различие.

Deduplicator 03.07.2022 22:29

@Deduplicator Хороший вопрос. То, что вы Можно используете переменную только для побочного эффекта конструктора/деструктора, не означает, что вы должен. Скрытие семантики делает код очень неясным.

Barmar 03.07.2022 22:39

@tstanisl Однако синтаксис foo(&(int){0}) полностью инициализирует объект перед передачей его адреса, в то время как синтаксис int a = foo(&a); не затрагивает память a до тех пор, пока foo() не вернется. Еще одно небольшое и тонкое отличие, которое могло изменить семантику программы.

cmaster - reinstate monica 04.07.2022 00:02

@cmaster-reinstatemonica: Если вы когда-либо действительно хотели сделать это на C, тривиально обернуть {} объявление, которое включает вызов функции. Тогда это становится законным даже в C89. Тем не менее, это странная особенность грамматики, которая необходима, но на самом деле ее не нужно исправлять, поскольку всегда есть эквивалентный обходной путь, даже для целей оптимизации, в (надеюсь, во всех) реальных компиляторах.

Peter Cordes 04.07.2022 09:36

@PeterCordes Я никогда не собирался так злоупотреблять C. Я просто отметил тот факт, что два варианта действительно демонстрируют различное поведение, т. е. что они не эквивалентны. Это не означает, что я когда-либо буду полагаться на эту разницу в поведении. В конце концов, код должен быть максимально простым.

cmaster - reinstate monica 04.07.2022 10:11

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