Тип пустоты в C - PullRequest
       46

Тип пустоты в C

0 голосов
/ 29 декабря 2018

Тип void в C кажется странным в разных ситуациях.Иногда он ведет себя как обычный тип объекта, такой как int или char, а иногда он просто ничего не значит (как и должно быть).

Посмотрите на мой фрагмент.Прежде всего, кажется странным, что вы можете объявить объект void, то есть вы просто ничего не объявляете.

Затем я создал переменную int и преобразовал ее результат в void, отбрасывая его:

Если выражение любого другого типа оценивается как пустое выражение, его значение или обозначение отбрасывается. (ISO / IEC 9899: 201x, 6.3.2.2 void)

Я пытался вызвать свою функцию с использованием void, но мой компилятор дал мне (Clang 10.0):

error: too many arguments to function call, expected 0, have 1

Таким образом, void в прототипе означает ничего , а не тип void.

Но затем я создал указатель на void, разыменовал его и присвоил « результат » моей переменной int.Я получил ошибку « несовместимый тип ». Это означает, что тип void здесь существует.

extern void a; // Why is this authorised ???

void foo(void); // This function takes no argument. Not the 'void' type.

int main(void)
{
    int a = 42;
    void *p;

    // Expression result casted to 'void' which discards it (per the C standard).
    (void)a;

    // Casting to 'void' should make the argument inexistant too...
    foo((void)a);

    // Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
    a = *p;

    // Am I not passing the 'void' type ?
    foo(*p);

    return 0;
}

Является ли void фактическим типом или ключевым словом, ничего не значащим?Потому что иногда он ведет себя как инструкция «, здесь ничего не разрешено », а иногда как фактический тип.

EDIT : это вопросы NOT дубликат.Это чисто семантика типа void.Я не хочу никаких объяснений о том, как использовать void, указатели на void или любые другие вещи.Я хочу получить ответ по стандарту C.

Ответы [ 5 ]

0 голосов
/ 30 декабря 2018

Прежде всего, кажется странным, что вы можете объявить пустой объект, то есть вы просто ничего не объявляете.

void - это неполный тип объекта, который не может быть завершен.Это в основном определяет его использование в обычных контекстах, то есть контекстах, которые не обеспечивают специальной обработки для void.Ваша декларация extern является одним из таких регулярных контекстов.Можно использовать неполный тип данных в неопределяемой декларации.

Однако вы никогда не сможете предоставить соответствующее определение для этой декларации.

Так что voidв прототипе ничего не значит, а не тип void.

Правильно.Параметр должен быть безымянным.И комбинация (void) получает специальную обработку : это не один параметр типа void, а скорее никаких параметров вообще.

Но затем я создалуказатель на void, разыменование его и присвоение «результата» моей переменной int.Я получил ошибку «несовместимый тип».Это означает, что тип void здесь существует.

Нет.Недопустимо применять унарный оператор * к указателю void *.Ваш код уже недействителен по этой причине.Ваш компилятор выдал вводящее в заблуждение диагностическое сообщение.Формально диагностические сообщения не требуются для правильного описания причины проблемы.Компилятор мог просто сказать «Привет!».

Является ли void фактическим типом или ключевым словом ничего не значит?

Это тип.Это неполный тип объекта, который не может быть завершен.

0 голосов
/ 29 декабря 2018

Из стандарта C # 6.2.5p19:

19 Тип void содержит пустой набор значений;это неполный тип объекта, который не может быть завершен.

Это указывает на то, что тип void существует.

Сомнение 1:

void foo(void); // This function takes no argument. Not the 'void' type.

Правильно.
От CСтандарт # 6.7.6.3p10 [выделение шахты] :

10 Особый случай безымянного параметра типа void в качестве единственного элемента в списке указывает, что функция не имеет параметров .

Это особый случай, который им пришлось добавить к синтаксису языка, потому что void foo(); уже означало что-то другое (void foo(); ничего не указывает в параметрах foo).Если бы не старое значение void foo();, void foo(); было бы синтаксисом для объявления функции без аргументов.Вы не можете ничего обобщить из этого.Это просто особый случай.

Сомнение 2:

// Casting to 'void' should make the argument inexistant too...
foo((void)a);

Нет, не будет, потому что void также является типом объекта, хотя и неполным.

Сомнение 3:

// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;

Да, оно существует, и поэтому компилятор сообщает об ошибке в этом утверждении.

Сомнение 4:

// Am I not passing the 'void' type ?
foo(*p);

Объявление функции foo():

void foo(void);
         ^^^^

В списке параметров void указано, что функция не будет принимать никаких аргументов, поскольку она была объявлена ​​с помощьюбез параметров.
Просто для справки, проверьте это из C Standard # 5.1.2.2.1p1 [выделение шахты] :

1 Функция, вызываемая при запуске программы, называетсяглавный.Реализация не объявляет прототип для этой функции.Он должен быть определен с типом возврата int и без параметров :

    int main(void) { /* ... */ }
             ^^^^

Сомнение 5:

extern void a; // Why is this authorised ???

Это разрешено, потому что void является допустимым типом, и это просто объявление.Хранилище не будет выделено для a.

0 голосов
/ 29 декабря 2018

В языке C введен тип void со значением «не волнует» больше, чем «ноль» или «ничего», и он используется для различных областей.

void ключевое слово может ссылаться на void type, reference to void, void expression, void operand или void function.Он также явно определяет функцию, не имеющую параметров.

Давайте рассмотрим некоторые из них.


Тип void

Прежде всего void объект существует и обладает некоторыми специальными свойствами, как указано в ISO / IEC 9899: 2017, §6.2.5 Типы :

Тип void содержит пустой набор значений;это неполный тип объекта, который не может быть завершен.

Указатели

Чем полезнее reference to void, либо void *, это ссылка нанеполный тип, но сам по себе хорошо определен, а затем является полным типом, имеет размер и может использоваться как любая другая стандартная переменная, как указано в ISO / IEC 9899: 2017, §6.2.5 Типы :

Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа.

Аналогично, указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь одинаковое представление и выравнивание.требования.

Все указатели на типы конструкций должны иметь те же требования к представлению и выравниванию, что и другие.

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

Указатели на другие типы не обязательно должны иметь одинаковые требования к представлению или выравниванию.


Приведение к void

Может бытьиспользуется как cast для аннулирования выражения, но позволяет завершить любой побочный эффект такого выражения.Эта концепция объясняется в стандарте в ИСО / МЭК 9899: 2017, §6.3 Преобразования, §6.3.2.2 void :

  1. (не существует)Значение выражения void (выражение с типом void) не должно использоваться никоим образом, и неявные или явные преобразования (за исключением void) не должны применяться к такому выражению.

    Если выражениелюбой другой тип оценивается как пустое выражение, его значение или обозначение отбрасывается.( Пустое выражение оценивается на предмет его побочных эффектов. )

Практическим примером приведения к void является его использование для предотвращения предупреждения онеиспользуемые параметры в определении функции:

int fn(int a, int b)
{
    (void)b;    //This will flag the parameter b as used 

    ...    //Your code is here

    return 0;
}

Приведенный выше фрагмент демонстрирует стандартную практику, используемую для отключения предупреждений компилятора.Приведение к void параметра b действует как эффективное выражение, которое не генерирует код и помечает b как используемое, предотвращая появление жалоб компилятора.


void Функции

Абзац §6.3.2.2 void стандарта также охватывает некоторые пояснения о void функциях, которые являются такими функциями, которые не возвращают никакого значения, пригодного для использования в выражении, но функции вызываются в любом случаедля реализации побочных эффектов.


void свойства указателей

Как мы уже говорили, указатели на void гораздо более полезны, поскольку они позволяют обрабатывать ссылки на объекты в общем видеиз-за их свойства, объясненного в ISO / IEC 9899: 2017, §6.3.2.3 указателей :

  1. Указатель на void может быть преобразован в или изуказатель на любой тип объекта.

    Указатель на любой тип объекта может быть преобразован в указатель на void и обратно;результат должен сравниться с исходным указателем .

В качестве практического примера представьте функцию, возвращающую указатель на разные объекты в зависимости от входных параметров:

enum
{
    FAMILY,     //Software family as integer
    VERSION,    //Software version as float
    NAME        //Software release name as char string
} eRelease;

void *GetSoftwareInfo(eRelease par)
{
    static const int   iFamily  = 1;
    static const float fVersion = 2.0;
    static const *char szName   = "Rel2 Toaster";

    switch(par)
    {
        case FAMILY:
            return &iFamily;
        case VERSION:
            return &fVersion;
        case NAME:
            return szName;
    }
    return NULL;
}

В этом фрагменте вы можете вернуть общий указатель, который может зависеть от входного значения par.


void в качестве параметра функции

Использование параметра void в определениях функций было введено после, так называемого, стандарта ANSI, для эффективного устранения неоднозначности функций с переменным числом аргументов у функций, имеющих нет.аргументы .

Из стандарта ИСО / МЭК 9899: 2017, 6.7.6.3 Деклараторы функций (включая прототипы) :

Особый случай безымянного параметра типа void в качестве единственного элемента в списке указывает, что у функции нет параметров.

Фактические компиляторы все еще поддерживают объявление функции с пустымскобки для обратной совместимости, но это устаревшая функция, которая в конечном итоге будет удалена в следующем выпуске стандарта.См. Дальнейшие указания - §6.11.6 Деклараторы функций :

  1. Использование деклараторов функций с пустыми скобками (не деклараторы типа параметров формата прототипа) является устаревшей функцией.

Рассмотрим следующий пример:

int foo();         //prototype of variable arguments function (backward compatibility)
int bar(void);     //prototype of no arguments function
int a = foo(2);    //Allowed
int b = foo();     //Allowed
int c = bar();     //Allowed
int d = bar(1);    //Error!

Теперь напоминаем ваш тест, если мы вызываем функцию bar следующим образом:

int a = 1;
bar((void)a);

Вызывает ошибку, потому что приведение к void объекта не обнуляет его.Таким образом, вы все еще пытаетесь передать объект void в качестве параметра функции, у которой его нет.


Побочные эффекты

По запросу это краткое объяснение побочные эффекты concept.

Побочным эффектом является любое изменение объектов и значений, полученных в результате выполнения оператора и не являющееся прямым ожидаемым эффектом.

int a = 0;
(void)b = ++a;

В приведенном выше фрагменте выражения void теряется прямой эффект, присваивающий b, но в качестве побочный эффект увеличивается значение a.

Единственная ссылка, поясняющая значение,в стандарте можно найти в 5.1.2.3 Выполнение программы :

Доступ к энергозависимому объекту, изменение объекта, изменение файла или вызов функции, выполняющей любую из этих операций, являются побочными эффектами, которые являются изменениями в состоянии среды выполнения.

Оценка выражения в целом включает как вычисления значений, так и инициирование побочных эффектов .

0 голосов
/ 29 декабря 2018

void является типом.Согласно C 2018 6.2.5 19, тип не имеет значений (набор значений, которые он может представлять, пуст), он неполный (его размер неизвестен) и не может быть завершен (его размер не может быть известен).

Относительно extern void a;, это не определяет объект.Он объявляет идентификатор.Если бы a использовалось в выражении (кроме как часть оператора sizeof или _Alignof), его где-то в программе должно быть определение.Поскольку не существует определения void объекта в строго соответствующем C, a не может использоваться в выражении.Так что я думаю, что это объявление разрешено в строго соответствующем C, но бесполезно.Он может использоваться в реализациях C как расширение, которое позволяет получить адрес объекта, тип которого неизвестен.(Например, определите фактический объект a в одном модуле, затем объявите его как extern void a; в другом модуле и используйте &a там, чтобы получить его адрес.)

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

В foo((void) a) приведение не делает значение «не существует». Она преобразует a в тип void,Результатом является выражение типа void.Это выражение «существует», но оно не имеет значения и не может использоваться в выражении, поэтому использование его в foo((void) a) приводит к сообщению об ошибке.

0 голосов
/ 29 декабря 2018

В C , void нельзя рассматривать как тип данных , это ключевое слово, используемое в качестве заполнителя вместо типа данных, чтобы показать, что на самом деле тамнет данныхСледовательно, это

void a;

недопустимо.

, в то время как здесь ключевое слово

void foo(void); 

void используется, чтобы сообщить компилятору, что foo не собираетсяпринимать любой входной аргумент и не иметь возвращаемого типа.

В приведенном ниже случае

int a = 42;
void *p;
a = *p; /* this causes error */

a = *p; неверно, потому что вы не можете разыменовать void указатель напрямую, вам нужно выполнить правильный типкастинг первый.например,

a = *(int*)p; /* first typecast and then do dereference */

Также это

foo(*p);

неверно по двум причинам:

  • во-первых foo() не ожидает никаких аргументов.
  • во-вторых, вы не можете сделать *p, так как p является пустым указателем.Правильным является foo(*(int*)p);, если foo() объявление void foo(int);.

Обратите внимание, что

 (void)a;

ничего не делает, поэтому ваш компилятор может не выдавать предупреждениено когда вам нравится,

int b = (void)a;

компилятор не разрешит, поскольку void не считается типом данных.

Наконец, это

extern void a; // Why is this authorised ???

это простообъявление, а не определение, a не существует до тех пор, пока вы его не определите, поскольку a имеет класс хранения extern, вам нужно определить где-нибудь, а когда вы собираетесь определить как

a = 10;

Компилятор выдает ошибку как

Ошибка: 'a' имеет неполный тип

С C Стандартный 6.2.5 Типы

Тип void включает empty set of values;это неполный тип объекта , который не может быть завершен.

6.3.2.2 void

(несуществующее) значениеvoid выражения (выражение с типом void) не должно использоваться никоим образом, и неявные или явные преобразования (кроме void) не должны применяться к такому выражению .Если выражение любого другого типа оценивается как выражение void, его значение или обозначение отбрасывается .(Выражение void оценивается для его побочных эффектов.)

6.3.2.3 Указатели

A указатель на void может быть преобразован в или из указателя на любой тип объекта .Указатель на любой тип объекта может быть преобразован в указатель на void и обратно;результат должен сравниться с исходным указателем.

A storage-class specifier или спецификатор типа изменяет ключевое слово void как список типов параметров функции (6.7.6.3).

Попыткасделано для использования значения выражения void, или неявное или явное преобразование ( за исключением void ) применяется к выражению void (6.3.2.2).

...