2016-01-17 4 views
29

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

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

Этот вопрос вызывает ошибку GCC 4.8.3. Но это одна компилирует:

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

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

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

test(const void* vpointer, ...) 

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

+6

'const T *' означает, что данные, указываемые указателем to_ указателем, являются константами. Второй - очень хрупкий, поскольку он отбрасывает const. –

+0

@ColonelThirtyTwo Но из того, что я читал в других сообщениях stackoverflow, не const void * часто используется для предотвращения изменения данных? Но мне кажется, что путем литья его можно очень легко модифицировать. – MoneyBall

+0

@MoneyBall да, но кастинг - вот так. Вот почему кастинг опасен - я могу взять ваш «const int *» и передать его в «int64_t *» и SIGSVF или испортить другие данные. Проблема заключается в том, что * casting * не объявляет const. –

ответ

41
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 работает от символьных строк, memcpy работает с общими данными.В то время как я использую STRCPY, как, например, следующее обсуждение точно так же для обоих, но с char * и const char * для strcpy и void * и const void * для memcpy:

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

srcconst 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 
} 

Case 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 

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

+0

Прежде всего, спасибо за все примеры. Мне ясно, что даже если const устанавливает какой-то контракт между вызывающим и вызываемым, вызывающий может легко отбросить спецификатор const. Можно ли с уверенностью сказать, что константный определитель НЕ гарантирует, что значение, указанное указателем const void *, является постоянным? – MoneyBall

+5

@MoneyBall Можно с уверенностью сказать, что C дает вам способы стрелять в ногу (позволяя отбросить спецификатор const является одним из них). При этом можно предположить, что функция не отбрасывает спецификатор const. Если это не злонамеренная или прослушиваемая функция. – bolov

+2

@MoneyBall Я добавил примечание о компиляторе, применяющем этот контракт. – bolov

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

Нет, это не работает. Отбрасывание константы (с поистине const данных) неопределенное поведение и ваша программа, скорее всего, сбой, если, например, реализация поставит const данные в ПЗУ. Тот факт, что «он работает» не меняет того факта, что ваш код плохо сформирован.

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

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

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

+3

Фактически отбрасывание константы - это не неопределенное поведение. Он хорошо определен даже для изменения значения, указанного с условием, что объект был объявлен изменчивым. См. Мой ответ для подробного примера. Но я на 100% согласен с тем, что это не повод, позволяющий отбрасывать констебль. – bolov

+0

@bolov Да, спасибо за указание. Я просто сказал об этом, когда упомянул о реализации, которая помещает данные 'const' в ПЗУ. – edmz

3

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

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

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

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

Смежные вопросы