Что на самом деле делает объявление переменной `const int *__errno_location ()` внутри этого оператора catch?

Работая над упражнением для моего класса C++, где мы работали над обработкой исключений, я столкнулся с загадочной проблемой. Изначально у меня был следующий обработчик исключений (пожалуйста, не критикуйте использование int в качестве исключений, это была задача, которую нам дали):

#include <iostream>
//...

try {
    // Code that throws an int
}
catch(const int errno) {
    std::cout << errno << std::endl;
}

Неподготовленному глазу это может показаться обычным объявлением целочисленной переменной исключения. Однако небезопасный код продолжал обходить блок catch, несмотря на то, что сам выбрасывал целое число. После некоторого расследования выяснилось, что макрос в errno.h объявленном #define errno (*__errno_location ()) вызвал расширение макроса в следующий код

try {
    // Code that throws an int
}
catch(const int (*__errno_location())) {
    std::cout << (*__errno_location()) << std::endl;
}

Я не был уверен, что именно объявляется в выражении catch. Я попробовал использовать C Gibberish для английского, но неудивительно, что это не сильно помогло, сообщив мне, что это объявление функции, возвращающей int*.

Немного поигравшись с этим, я придумал следующий код:

const int *blah() {
    return new int(10);
}

int main() {
    try {
        throw &blah;
    }
    catch(const int *foo()) {
        std::cout << *foo () << std::endl;
    }
}

Который выводит на консоль число 10. Изменение значений в конструкторе целых чисел также приводит к выводу этого целого числа на консоль. Итак, моя текущая гипотеза заключается в том, что const int *foo() объявляет, что указатель функции, возвращающий указатель int, должен быть пойман в этом блоке catch, а в операторе печати мы просто вызываем эту функцию и разыменовываем указатель. Однако мне хотелось бы, чтобы мою гипотезу проверили или исправили более опытные программисты на C++.

В коде не должно было использоваться имя переменной errno. Это зарезервированное имя, и оно вызывает побочные эффекты. Замените errno на другое имя переменной, и все будет в порядке. Также обратите внимание, что рекомендуется перехватывать типы исключений с помощью const& (для int это не так важно). Так что ставьте catch(const int& value) std::cout << value << "\n" и все будет в порядке.

Pepijn Kramer 28.06.2024 15:21

Ваше исследование верно. В C++ можно использовать любой тип, включая функции и указатели на функции. Вы можете сделать его более читабельным, используя using FuncType = const int *(); и catch (FuncType foo).

Yksisarvinen 28.06.2024 15:26

Вызов оператора new в функции blah приводит к утечке памяти, поскольку соответствующий delete нигде не найден.

tbxfreeware 29.06.2024 12:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

errno определена реализация. См. cppreference:

errno — это макрос препроцессора, используемый для индикации ошибок. Он расширяется до статического (до C++11) локального потока (начиная с C++11) изменяемого lvalue типа int.

Из того, что вы уже узнали, мы можем сделать вывод, что __errno_location() — это функция (или какой-либо вызываемый объект), возвращающая указатель на int, который кодирует последнюю ошибку в используемой вами реализации.

Как упоминалось в комментарии, идентификатор errno зарезервирован POSIX.

Теперь то, что происходит в вашем коде, примерно следующее:

int* foo() { 
    static int x = 42;
    return &x;
}

int main() {
    try {

    } catch(const int (*foo()) ){

    }
}

Который тоже компилируется (см. здесь: https://godbolt.org/z/3v8nsPr74).

Причина в том, что const int (*foo()) на самом деле объявляет параметр с именем foo типа const int(*)(), то есть указатель на функцию без параметра и возвращает const int.

И, как вы уже выяснили, название — просто совпадение. Ваш параметр называется __errno_location, и внутри блока catch он затеняет фактическую функцию с тем же именем.

Урок, который нужно усвоить, заключается в том, насколько злы макросы. Они загрязняют глобальное пространство имен. Вот почему вы были удивлены, используя errno в качестве имени параметра. А во-вторых, макросы — это зло, потому что невинное на вид имя может превратить const int* во что-то совершенно иное. К сожалению, здесь у вас нет выбора не использовать макрос. Вам только нужно быть осторожным, чтобы избежать этого имени.

@Yksisarvinen он компилируется, потому что *__errno_location() — это изменяемое lvalue типа int… о, ок, теперь я понимаю, что вы имеете в виду. Как сильно я не люблю, когда меня принимают, когда я не прав :/

463035818_is_not_an_ai 28.06.2024 15:29

Удалил мой комментарий, так как ОП принял ваш ответ, поэтому я думаю, что я неправильно его интерпретировал.

Yksisarvinen 28.06.2024 15:30

@Yksisarvinen нет, я думаю, ты был прав. Я не понимаю, почему код компилируется, и я действительно не отвечал на этот вопрос, когда писал это.

463035818_is_not_an_ai 28.06.2024 15:31

Принял ответ до того, как просмотрел комментарии... Итак, я полагаю, загадка еще не раскрыта? Я не решаюсь сказать, что мы объявили переменную-указатель функции, потому что, придя к форме C, я ожидал, что объявление будет return_type (*name)(args...), что близко, но не совсем к тому, до чего расширяется фактический код.

Keheck 28.06.2024 15:32

@Кех, я пока не понимаю, почему это компилируется godbolt.org/z/TaxWsv51c ? Возможно, просто, но я этого не вижу, и мне нужно уйти, поэтому предлагаю вам на данный момент отказаться от этого.

463035818_is_not_an_ai 28.06.2024 15:34

@Keheck IMO, вы уже написали это в своем вопросе - он интерпретируется как тип функции, не принимает аргументов, возвращает const int*, и такой объект функции, когда он пойман, называется __errno_location.

Yksisarvinen 28.06.2024 15:35

Да, это теория, основанная на фактах, но мне интересно, почему ее можно так заявлять. Как я уже сказал, я ожидал, что синтаксис будет const int* (*foo)() или что-то подобное, поскольку именно так вы обычно объявляете указатели на функции (в данном случае функция, возвращающая int* без аргументов). Возможно, мне не хватает небольшой детали в C++.

Keheck 28.06.2024 15:37

@Keheck Деталь может заключаться в том, что существуют типы функций и типы указателей функций. Это очень запутанная особенность языка, поскольку, когда вы объявляете тип функции как параметр, вы действительно получаете указатель на функцию, а когда вы передаете функцию другой функции, и foo, и &foo преобразуются в указатель на функцию. И вызов указателя функции не требует предварительного разыменования указателя...

Yksisarvinen 28.06.2024 15:45

Возможно, моя формулировка была немного неясной. Я имел в виду, что мы вызываем функцию foo, а затем разыменовываем возвращаемый ею указатель int. Интересно, является ли разница между типами указателей функций и типами функций особенностью C++ или типы функций существуют и в C? Я знаю, что у него есть типы указателей на функции

Keheck 28.06.2024 15:53

@Keheck Я снова отредактировал, и теперь, по моему мнению, ответ полон. Однако все части уже присутствуют в вашем вопросе. Я лишь повторяю то, что вы уже сказали.

463035818_is_not_an_ai 28.06.2024 18:29

Похоже, что он анализируется как функция, не принимающая никаких параметров, возвращающих const int*: godbolt.org/z/hhx71jxrc (Обратите внимание, что catch настраивается так же, как и параметры функции, поэтому foo объявляется с типом функции и адаптируется к указателю - тип функции)

Artyer 28.06.2024 19:29

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