27.3.5. Преобразование указателей типа void*
В языке указатель типа void*
можно использовать как в правой части оператора присваивания, так и для инициализации указателей любого типа; в языке C++ это невозможно. Рассмотрим пример.
void* alloc(size_t x); /* выделяет x байтов */
void f (int n)
{
int* p = alloc(n*sizeof(int)); /* OK в языке C;
ошибка в языке C++ */
/* ... */
}
Здесь указатель типа void*
возвращается как результат функции alloc()
и неявно преобразовывается в указатель типа int*
. В языке C++ мы могли бы переписать эту строку следующим образом:
int* p = (int*)alloc(n*sizeof(int)); /* OK и в языке C,
и в языке C++ */
Мы использовали приведение в стиле языка C (раздел 27.3.4), чтобы оно оказалось допустимым как в программах на языке C, так и в программах на языке C++.
void*
в T*
является недопустимым в языке С++? Потому, что такие преобразования могут быть небезопасными.
void f()
{
char i = 0;
char j = 0;
char* p = &i
void* q = p;
int* pp = q; /* небезопасно; разрешено в языке C,
ошибка в языке C++ */
*pp = –1; /* перезаписываем память, начиная с адреса &i */
В данном случае мы даже не уверены, какой фрагмент памяти будет перезаписан: переменная j
или часть памяти, на которую ссылается указатель p
? А может быть, память, использованная для управлении вызовом функции f()
(стек функции f
)? Какие бы данные ни были перезаписаны, вызов функции f()
приведет к печальным последствиям.
Обратите внимание на то, что (обратное) преобразование указателя типа T*
в указатель типа void*
является совершенно безопасным, — вы не сможете придумать ужасные примеры, подобные предыдущему, — и они допускаются как в языке C, так и в языке C++.
К сожалению, неявное преобразование void*
в T*
широко распространено в языке C и, вероятно, является основной проблемой совместимости языков С и С++ в реальных программах (см. раздел 27.4).
27.3.6. Перечисление
В языке C можно присваивать целое число перечислению без приведения int
в enum
. Рассмотрим пример.
enum color { red, blue, green };
int x = green; /* OK в языках C и C++ */
enum color col = 7; /* OK в языке C; ошибка в языке C++ */
Одним из следствий этого факта является то, что в программах на языке С мы можем применять операции инкрементации (++
) и декрементации (––
) к переменным, являющимся перечислениями. Это может быть удобным, но одновременно небезопасным.
enum color x = blue;
++x; /* переменная x становится равной значению green;
ошибка в языке C++ */
++x; /* переменная x становится равной 3; ошибка в языке C++ */
Выход за пределы перечисления может входить в наши планы, а может быть неожиданным.
Обратите внимание на то, что, подобно дескрипторам структур, имена перечислений пребывают в своем собственном пространстве имен, поэтому каждый раз при указании имени перечисления перед ним следует ставить ключевое слово enum
.
color c2 = blue; /* ошибка в языке C: переменная color не находится
в пределах области видимости; OK в языке C++ */
enum color c3 = red; /* OK */
27.3.7. Пространства имен
В языке С нет пространств имен (в том смысле, как это принято в языке С++). Так что же можно сделать, чтобы избежать коллизий имен в больших программах, написанных на языке С? Как правило, для этого используются префиксы и суффиксы. Рассмотрим пример.
/* в bs.h: */
typedef struct bs_string { /* ... */ } bs_string; /* строка
Бьярне */
typedef int bs_bool; /* булев тип Бьярне */
/* in pete.h: */
typedef char* pete_string; /* строка Пита */
typedef char pete_bool; /* булев тип Пита */
Этот прием настолько широко используется, что использовать одно- и двухбуквенные префиксы обычно уже недостаточно.
27.4. Свободная память