Понимание точного значения ключевого слова "void" в C/С++
Как объяснено, например, здесь, мы все знаем о 3 основных применениях для ключевого слова void (более опытные программисты на C/С++ могут перейдите к четвертому использованию):
1) В качестве типа возврата для функции, которая ничего не возвращает. Эта вызовет пример кода следующим образом:
void foo();
int i = foo();
чтобы сгенерировать ошибку компилятора.
2) В качестве единственного параметра в списке параметров функции. AFAIK, пустой список параметров функции точно совпадает с компилятором, и поэтому следующие две строки идентичны по смыслу:
( edit:, это верно только в С++. Комментарии показывают разницу в c).
int foo();
int foo(void);
3) void * является специальным типом общего указателя - он может указывать на любую переменную, которая не объявляется с ключевым словом const или volatile, конвертировать в/из любого типа указателя данных и указывают на все функции, не являющиеся членами. Кроме того, он не может быть разыменован. Я не буду приводить примеры.
Существует также четвертое использование, которое я не совсем понимаю:
4) В условной компиляции он часто используется в выражении (void) 0 следующим образом:
// procedure that actually prints error message
void _assert(char* file, int line, char* test);
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#define assert(e) \
((e) ? (void)0 : \
__assert(__FILE__, __LINE__, #e))
#endif
Я пытаюсь понять поведение этого выражения в экспериментах. Все следующие юридические (хорошо компилируются):
int foo(); // some function declaration
int (*fooPtr)(); // function pointer
void(foo);
void(fooPtr);
void(0);
(void)0;
void('a');
void("blabla");
exampleClass e; //some class named exampleClass with a default ctor
void(e);
static_cast<void>(e);
но это не так:
void(0) // no semicolon
int i = void(0);
Могу ли я заключить из этого, что "void" (в контексте 4-го использования) - это просто специальный тип, который может использовать любой тип (будь то c-style или cpp- стиль), и он никогда не может использоваться как lvalue или rvalue?
Ответы
Ответ 1
Могу ли я заключить из этого, что "void" (в контексте 4-го использования) - это просто особый тип, который может наложить любой тип (будь то c-style или cpp-style), и он никогда не сможет использоваться как lvalue или rvalue?
Джеймс Макнеллис указал в комментарии выше, что void
может использоваться как rvalue через выражение void()
.
Он процитировал это из текущего стандарта С++:
С++ 11 §5.2.3/2:
"Выражение T()
, где T
- спецификатор простого типа или typename-specifier для типа объекта без массива или типа (возможно cv-qualit) void
, создает значение знака указанного типа, который инициализируется значением (инициализация не выполняется для случая void()
).
Это позволяет писать код типа & hellip;
template< class T >
T foo() { return T(); }
и используйте foo<void>()
(скорее всего, из другого шаблонного кода, я бы предположил).
Формально void
является неполным типом, который никогда не может быть завершен.
Приветствия и hth.
Ответ 2
Что касается вашего 2), в C:
int foo();
int foo(void);
не эквивалентны.
Первое - это объявление старого стиля (все еще поддерживается в последнем стандарте C), в котором говорится, что foo
принимает фиксированное, но неопределенное число и тип аргументов. Вызов типа foo(42)
не требует диагностики, но его поведение undefined, если в определении foo
не указано, что он имеет один параметр int
.
Второй говорит, что foo
не принимает аргументов, а foo(42)
требует диагностики. Вторая декларация является прототипом; первое - нет. Синтаксис (void)
был добавлен, потому что необходим специальный синтаксис, чтобы объявить функцию без параметров без записи того, что выглядит как декларация старого стиля.
В новом C-коде вы всегда должны использовать прототипы. Декларации старого стиля хранятся на языке только для поддержки старого кода.
С++ отбрасывает объявления старого стиля. В С++ два объявления foo
эквивалентны; форма int foo(void)
поддерживается только для совместимости с C.
Ваш случай 4) действительно не имеет ничего общего с условной компиляцией. Выделение выражения для void
- это способ оценки выражения и явное отбрасывание его значения. Он часто используется для подавления предупреждений. Например, если foo()
возвращает результат int
, то
foo();
как автономный оператор может вызвать предупреждение о том, что вы отбрасываете результат, но
(void)foo();
вероятно, сообщает компилятору, что вы намеревались это сделать.
void
- неполный тип, который не может быть завершен. Это означает, что выражение типа void
может использоваться только в контексте, который не ожидает значения. И вы правы, выражение типа void
не может использоваться как lvalue или rvalue. EDIT: кроме случая, описанного Alf, и только в С++.
Ответ 3
Void - это тип без значений. Так как он не имеет значений, выражения типа void
могут использоваться только для их побочных эффектов. Вот некоторые исправления:
# 1 Да.
# 2 Как уже упоминалось, корректно в С++, но неверно в C.
int foo(); // In C, this is an "old-style" prototype
// which doesn't declare its parameters
// Definition for the above function: valid in C, not valid in C++
int foo(int x, int y) { return x + y; }
# 3 void*
- особый тип общего указателя: не совсем. Он не переносится для указания указателя на void*
и обратно.
void func(void);
void *x = func;
void (*f)(void) = func; // NOT portable
Однако вы можете указать любой указатель на любой другой тип указателя функции в C и обратно, не беспокоясь о переносимости.
# 4 Также используется для явного указания компилятору, что значение не требуется.
// Generates "unused parameter warning"
int func(int x)
{
return 3;
}
// Does not generate any warnings
int func(int x)
{
(void) x;
return 3;
}
Я рекомендую всегда включать ненужные предупреждения параметров для ваших проектов, потому что они часто позволяют вам улавливать простые опечатки.
Ответ 4
void
всегда является неполным типом, что означает, что вы никогда не сможете создать экземпляр какого-либо объекта типа void
. Следовательно, не может быть никаких значений этого типа, ни L, ни R. Не может быть никакого lvalue этого типа, хотя void()
является (p) rvalue. [Спасибо @James!]
Для void*
приходят в голову три общих применения: 1) он достаточно большой, чтобы удерживать любой указатель объекта. 2) dynamic-cast to void pointer дает указатель на наиболее производный тип. 3) это тип указателей необработанной памяти, т.е. Тип результата std::malloc()
и ::operator new()
, тип аргумента std::free()
и ::operator delete()
, а также тип размещения - новый. Он не должен использоваться ни для чего другого в строгом С++, хотя он также используется в последней роли как аргумент buffer для fread
и fwrite
в библиотеке C.