Понимание точного значения ключевого слова "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.