Когда использовать const void*?

у меня есть эта очень простая тестовая функция, которую я использую, чтобы выяснить, что происходит с const qualifier.

int test(const int* dummy)
{
   *dummy = 1;
   return 0;
}

Это бросает мне ошибку с GCC 4.8.3. И все же этот компилирует:

int test(const int* dummy)
{
   *(char*)dummy = 1;
   return 0;
}

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

недавно я видел коды, которые использовать

test(const void* vpointer, ...)

по крайней мере, для меня, когда я использовал void*, я склонен бросать его в char* для арифметики указателя в стеках или для трассировки. Как const void* запретить функциям подпрограммы изменять данные, при которых vpointer указывает?

3 ответов


const int *var;

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

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

этот" контракт " выполняется компилятором. *dummy = 1 не скомпилируется. Приведение-это способ обойти это, сказав компилятору, что вы действительно знаете, что делаете, и позволяете вам это делать. К сожалению, "я действительно знаю, что делаю" обычно не так.

const также может использоваться компилятором для выполнения оптимизации, иначе это невозможно.


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

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

подробнее об этом в конце поста


что касается мотивации и использования, давайте рассмотрим аргументы strcpy и memcpy функции:

char* strcpy( char* dest, const char* src );
void* memcpy( void* dest, const void* src, std::size_t count );

strcpy работает со строками типа char, memcpy работает на общих данных. В то время как я использую strcpy в качестве примера, следующее обсуждение точно так же для обоих, но с char * и const char * на strcpy и void * и const void * на memcpy:

dest is char * потому что в буфере dest функция поместит копию. Функция будет изменять содержимое этого буфера, поэтому она не является const.

src и const char * потому что функция считывает только содержимое буфера src. Она не изменяет его.

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


const и void ортогональны. Вот и вся дискуссия выше о const относится к любому типу (int, char, void, ...)

void * используется в C для "общих" данных.


еще больше о неопределенном поведении:

Пример 1:

int a = 24;
const int *cp_a = &a; // mutabale to const is perfectly legal. This is in effect
                      // a constant view (reference) into a mutable object

*(int *)cp_a = 10;    // Legal, because the object referenced (a)
                      // is declared as mutable

случае 2:

const int cb = 42;
const int *cp_cb = &cb;
*(int *)cp_cb = 10;    // Undefined Behavior.
                       // the write into a const object (cb here) is illegal.
void foo(const int *cp) {
    *(int *)cp = 10;      // Legal in case 1. Undefined Behavior in case 2
}

Пример 1:

int a = 0;
foo(&a);     // the write inside foo is legal

Пример 2:

int const b = 0;
foo(&b);     // the write inside foo causes Undefined Behavior

еще раз подчеркну: если вы действительно не знаете, что делаете, и все люди, работающие в настоящем и будущем над кодом, являются экспертами и понимают это, и у вас есть хорошая мотивация, если все вышесказанное не выполняется, никогда не отбрасывайте constness!!


int test(const int* dummy)
{
   *(char*)dummy = 1;
   return 0;
}

нет, это не работает. Отбросив Констанцию (с истинно const data) является неопределенное поведение и ваша программа, скорее всего, рухнет, если, например, реализация put const данные в ROM. Тот факт, что" это работает", не меняет того факта, что ваш код плохо сформирован.

по крайней мере, для меня, когда я использовал void*, я склонен бросать его в char * для арифметика указателя в стеках или для трассировки. Как может const void* предотвращать функции подпрограммы от изменения данных, на которых vpointer указывает?

A const void* означает указатель на некоторые данные, которые нельзя изменить. Чтобы прочитать его, да, вы должны привести его к конкретным типам, таким как char. Но я сказал ... --8-->чтение, а не писать, который, опять же, является UB.

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


возможно, что данный компилятор на данной ОС может поместить некоторые из своих const данные на страницах памяти только для чтения. Если это так, попытка записи в это место приведет к сбою оборудования, например, к общей ошибке защиты.

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

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

С моей головы, один редкий пример, где это может иметь смысл: предположим, у вас есть библиотека, которая передает параметры дескриптора. Как он генерирует и использует их? Внутренне, они могут быть указателями на структуры данных. Так что это приложение, где вы могли бы typedef const void* my_handle; таким образом, компилятор выдаст ошибку, если ваши клиенты попытаются разыменовать ее или сделать арифметику по ошибке, а затем вернет ее к указателю на вашу структуру данных внутри ваших библиотечных функций. Это не самая безопасная реализация, и вы хотите быть осторожны с злоумышленниками, которые могут пройти произвольные значения для вашей библиотеки, но это очень низкие накладные расходы.