Символьные массивы или указатели на char в структурах
В рассмотренных ранее примерах для хранения строк в структуре применялись символьные массивы. Вероятно, вас интересует, можно ли вместо них использовать указатели на char? Например, в листинге 14.3 имеется следующее объявление:
#define LEN 20
struct names {
char first[LEN]; char last[LEN];
};
Можно ли вместо этого поступить так?
struct pnames { char * first; char * last;
};
Ответ — да, это возможно, но могут возникнуть проблемы, если вы не обдумаете все последствия. Взгляните на показанный ниже код:
struct names veep = {"Talia", "Summers"};
struct pnames treas = {"Brad", "Fallingjaw"};
printf("%s и %s\n", veep.hrst, treas.hrst);
588 Глава 14
Этот код допустим, и он работает, однако рассмотрим, где хранятся строки. В случае переменной veep типа struct names строки хранятся внутри структуры; для хранения двух имен структура выделяет всего 40 байтов. Тем не менее, в переменной treas типа struct pnames строки хранятся там, где компилятор сохраняет строковые константы. Все, что содержит данная структура — это два адреса, которые в нашей системе в целом занимают 16 байтов. В частности, структура struct pnames не выделяет память для хранения строк. Она может применяться только со строками, для которых память была выделена где-то в другом месте, такими как строковые константы или строки в массивах. Короче говоря, указатели в структуре struct pnames должны использоваться только для управления строками, которые были созданы с выделением под них памяти в другом месте программы.
Давайте посмотрим, когда это ограничение превращается в проблему. Взгляните на следующий код:
struct names accountant;
struct pnames attorney;
puts("Введите фамилию вашего бухгалтера );
scanf("%s", accountant.last);
puts("Введите фамилию вашего адвоката:");
scanf("%s", attorney.last); /* здесь скрыта опасность */
С точки зрения синтаксиса этот код допустим. Но куда сохраняются входные данные? Фамилия бухгалтера записывается в последний член переменной accountant; эта структура содержит массив для хранения строки. В случае фамилии адвоката функция scanf() получает указание поместить строку фамилии но адресу, заданному как attorney. last. Из-за того, что эта переменная не инициализирована, адрес может иметь произвольное значение, и программа может попытаться поместить фамилию куда угодно. Если повезет, то программа будет работать, по крайней мере, некоторое время, либо сразу же аварийно завершится. Однако если программа работает, то вам на самом деле не повезло, т.к. в ней присутствует катастрофическая ошибка, о которой вы не знаете.
Таким образом, если вам необходима структура для хранения строк, то проще применять члены типа символьных массивов. Использование указателей на char в отдельных случаях допускается, но потенциально сопряжено с серьезными проблемами.
Структура, указатели и malloc()
Применение в структуре указателя, поддерживающего строку, имеет смысл, когда с помощью функции malloc() для строки выделяется область памяти и указатель используется, чтобы сохранить адрес этой области. Преимущество такого подхода заключается в том, что malloc() позволяет выделить ровно столько памяти, сколько необходимо для строки. Вы можете запросить 4 байта для сохранения строки "Joe" и 18 байтов для строки с мадагаскарским именем "Rasolofomasoandro". Код в листинге 14.9 совсем нетрудно адаптировать под этот подход. Два основных изменения касаются определения структуры с целью применения указателей вместо массивов и предоставления новой версии функции getinfo().
Новое определение структуры будет выглядеть следующим образом:
struct namect {
char * fname; // использование указателей вместо массивов char * lname; int letters;
};
Структуры и другие формы данных 589
Новая версия getinfo() будет читать входные данные во временный массив, использовать malloc() для выделения пространства памяти и копировать в него строку. Она будет делать это для каждого имени:
void getinfo (struct namect * pst)
{
char temp[SLEN];
printf("Введите свое имя.\n");
s_gets(temp, SLEN);
// выделение памяти для хранения имени pst->fname = (char *) malloc(strlen(temp) + 1);
// копирование имени в выделенную память strcpy(pst->fname, temp); printf("Введите свою фамилию.\n"); s_gets(temp, SLEN);
pst->lname = (char *) malloc(strlen(temp) + 1); strcpy(pst->lname, temp);
}
Вы должны четко понимать, что эти две строки не хранятся в структуре. Они сохранены в области памяти, управляемой malloc(). Тем не менее, адреса двух строк хранятся в структуре, и именно с ними обычно имеют дело функции обработки строк. Следовательно, остальные функции в программе в изменениях не нуждаются.
Однако, согласно совету из главы 12, вы должны уравновешивать вызовы malloc() вызовами free() , поэтому в программу добавлена новая функция по имени cleanup(), которая освобождает память, когда программа завершает пользоваться ею. Вы найдете эту новую функцию и оставшуюся часть программы в листинге 14.10.
Читать дальше