Использование типа указателя void в функции C

#c #casting

#c #Кастинг

Вопрос:

Я читаю книгу, в которой есть следующий код:

 int list_ins_next(List *list, ListElmt *element, const void *data) {  ListElmt *new_element;  /* Allocate storage for the element.*/  if ((new_element = (ListElmt *)malloc(sizeof(ListElmt))) == NULL)  return -1;  /* Insert the element into the list.*/  new_element-gt;data = (void *)data;  /* and so on */ }  

Как мы видим, функция получает const void *data в качестве одного из аргументов.

Почему он повторно отлит в строке: new_element-gt;data = (void *)data; ?

Дополнительная информация:

 /* Define a structure for linked list elements. */ typedef struct ListElmt_ {  void *data;  struct ListElmt_ *next; } ListElmt;  /* Define a structure for linked lists.*/ typedef struct List_ {  ListElmt *head;  ListElmt *tail; } List;  

Комментарии:

1. Он удаляет const , сообщая компилятору , что, хотя функция обещала не изменять память, на которую указывает, на data самом деле это совершенно нормально, и поэтому указатель может храниться в неконстантном значении, которое не дает таких гарантий. ИЛИ… указатель не является константой, НО будет рассматриваться как константа («клянусь мизинцем»). Без актерского состава это было бы ошибкой. На самом деле это немного плохое поведение, но такое часто случается в C.

2.Это не обязательно хорошо. Представьте, что вы передаете фактический const указатель на функцию. Это в основном удалило const бы спецификатор. Поэтому, если код, использующий этот связанный список, сам не отслеживает это, он может скрыть некоторые потенциальные ошибки. Например, когда разработчик хорошо отслеживает const строковые литералы. Они не получат предупреждения, и позже какой-нибудь другой код может получить доступ к указателю на строковый литерал и попытаться изменить его. Это больше не приведет к появлению предупреждений, но может спровоцировать нарушение доступа.

3. Действительно. Я имел в виду, что актеры утверждают, что все «в порядке». Предупреждение/ошибка, которую обходит приведение, существует именно для того, чтобы выявить случайное неправильное использование. И существование приведения здесь для того, чтобы гарантировать компилятору, что правила будут соблюдаться, несмотря на то, что в противном случае это выглядит сомнительно. Невыполнение этого обещания приведет к неопределенному поведению.

4. @paddy почему бы не опубликовать это в качестве ответа?

5. Конечно. Я так и сделал. В некотором роде рассказывая истории. Вот где сегодня моя голова.

Ответ №1:

Без приведения и с включенными достаточными уровнями предупреждений компилятора ваша компиляция завершилась бы неудачей с чем-то вроде:

предупреждение: назначение удаляет квалификатор ‘const’ из целевого типа указателя

Это означает, что следующее задание нарушает некоторые правила:

 new_element-gt;data = data; // lt;-- danger!  

Что случилось?

Ну, в сообщении говорится, что в результате назначения отбрасывается «классификатор констант». Давайте разберемся, что это означает.

Переменная data имеет тип const void* , что означает, что память, на которую она указывает, никогда не должна изменяться в результате следования этому указателю. Другими словами, функции list_ins_next не разрешается делать ничего, что приведет к изменению этой памяти.

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

Некоторая память действительно не может быть изменена. Примером могут служить строковые литералы. Если у вас есть строка "hello" в вашей программе, она на самом деле хранится где-то в памяти как часть кода. И у вас может быть указатель: const char* hello = "hello"; … но вам действительно не следует бросать const и начинать возиться с этими данными.

Вы видите в чем проблема? Если бы это было разрешено, то функция list_ins_next могла бы неосознанно нарушать правила. Даже несмотря на то , что ему запрещено изменять память, на которую указывает data , он хранит этот указатель в структуре, не связанной такими ограничениями. Позже кто-то, использующий эту структуру, мог бы с радостью пойти и использовать data ее так, как если бы она не была постоянной, не осознавая, что она должна быть постоянной.


Время аналогии (непослушный)

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

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

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


Хорошее поведение

Проблема здесь в том, что компилятор хочет уберечь нас от случайных ошибок, где бы он ни мог. Он справедливо предупреждает нас: эй, это плохо, возможно, вы используете это неправильно.

А теперь вернемся к тому, зачем здесь актерский состав. Это немного трудно сказать наверняка без дополнительного контекста. Что я скажу, так это то, что актеры дают обещание:

Дорогой компилятор C, этим актерским составом я торжественно клянусь, что не собираюсь нарушать правила, или, может быть, я мог бы нарушить правила, но я действительно действительно знаю, что делаю, и все последствия, поэтому, пожалуйста, давайте просто притворимся, что этот указатель const не является const.

Спасибо

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


Время аналогии (приятно)

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

Рядом с ним есть записная книжка, содержащая подробную информацию о том, что у вас есть и сколько денег вы, вероятно, потратили на выпивку. Там также есть записка с вопросом, не подумаете ли вы как-нибудь организовать дегустацию виски.


Плохое поведение

Однако, к сожалению, это не самая распространенная причина для отказа от const.

То, что мы очень часто видим, — это добавление приведения кем-то, кто просто хотел обойти ошибку компилятора, не полностью ее понимая. Компилятор сказал: «Вау, приятель, подожди», а программист сказал: «Пожалуйста, заткнись, чтобы я мог запустить свой код».

И, ты знаешь… У меня такое чувство, что автор этой книги, возможно, относится ко второй категории, но я не буду прямо утверждать это, потому что я не видел полного контекста примера кода, чтобы прокомментировать, почему он был написан таким образом.

At face value, either the list should be storing const data or the insert function should be accepting non-const data. End of story.


Summary

Мы обычно говорим «слепки скрывают ошибки». Имейте это в виду. Когда вы бросаете, делайте это с намерением. Делайте это, потому что вы знаете, что это действительно, а не только потому, что язык позволит вам это сделать. C с радостью позволит вам поднести гвоздодер к ноге и нажать на спусковой крючок.

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

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