Существует ситуация, когда добавление или удаление квалификатора const
к аргументу функции является серьезной ошибкой.Это происходит, когда вы передаете аргумент по указателю .
Вот простой пример того, что может пойти не так.Этот код не работает в C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// char * strncpy ( char * destination, const char * source, size_t num );
/* Undeclare the macro required by the C standard, to get a function name that
* we can assign to a pointer:
*/
#undef strncpy
// The correct declaration:
char* (*const fp1)(char*, const char*, size_t) = strncpy;
// Changing const char* to char* will give a warning:
char* (*const fp2)(char*, char*, size_t) = strncpy;
// Adding a const qualifier is actually dangerous:
char* (*const fp3)(const char*, const char*, size_t) = strncpy;
const char* const unmodifiable = "hello, world!";
int main(void)
{
// This is undefined behavior:
fp3( unmodifiable, "Whoops!", sizeof(unmodifiable) );
fputs( unmodifiable, stdout );
return EXIT_SUCCESS;
}
Проблема здесь с fp3
.Это указатель на функцию, которая принимает два const char*
аргумента.Однако он указывает на стандартный вызов библиотеки strncpy()
¹, первым аргументом которого является буфер, который он изменяет .Таким образом, fp3( dest, src, length )
имеет тип, который обещает не изменять данные, на которые указывает dest
, но затем передает аргументы в strncpy()
, который изменяет эти данные!Это возможно только потому, что мы изменили сигнатуру типа функции.
Попытка изменить строковую константу - это неопределенное поведение - мы фактически сказали программе вызывать strncpy( "hello, world!", "Whoops!", sizeof("hello, world!") )
- и на нескольких различных компиляторах, с которыми я тестировал,во время выполнения он молча завершится ошибкой.
Любой современный компилятор C должен разрешить присвоение fp1
, но предупреждает вас, что вы стреляете себе в ногу либо с помощью fp2
или fp3
.В C ++ строки fp2
и fp3
не будут компилироваться вообще без reinterpret_cast
.Добавление явного приведения заставляет компилятор предполагать, что вы знаете, что вы делаете, и заставляет глушить предупреждения, но программа все равно не работает из-за своего неопределенного поведения.
const auto fp2 =
reinterpret_cast<char*(*)(char*, char*, size_t)>(strncpy);
// Adding a const qualifier is actually dangerous:
const auto fp3 =
reinterpret_cast<char*(*)(const char*, const char*, size_t)>(strncpy);
Это не происходит с аргументами, передаваемыми по значениюпотому что компилятор делает копии тех.Пометка параметра, переданного значением const
, означает, что функция не должна изменять свою временную копию.Например, если стандартная библиотека внутренне объявлена char* strncpy( char* const dest, const char* const src, const size_t n )
, она не сможет использовать идиому K & R *dest++ = *src++;
.Это изменяет временные копии функции аргументов, которые мы объявили const
.Поскольку это не влияет на остальную часть программы, C не возражает, если вы добавите или удалите квалификатор const
, подобный описанному в прототипе функции или указателе функции.Обычно вы не делаете их частью общедоступного интерфейса в заголовочном файле, так как они являются деталями реализации.
¹ Хотя я использую strncpy()
в качестве примера известной функции справильная подпись, она вообще устарела.