Многие проектные решения, связанные с C, проистекают из того факта, что при первоначальной реализации передача параметров была несколько дорогой. Имеется выбор между, например,
void add_element_to_next(arr, offset)
char[] arr;
int offset;
{
arr[offset] += arr[offset+1];
}
char array[40];
void test()
{
for (i=0; i<39; i++)
add_element_to_next(array, i);
}
против
void add_element_to_next(ptr)
char *p;
{
p[0]+=p[1];
}
char array[40];
void test()
{
int i;
for (i=0; i<39; i++)
add_element_to_next(arr+i);
}
последняя была бы немного дешевле (и, следовательно, предпочтительнее), поскольку требовалась только передача одного параметра, а не двух. Если вызываемому методу не нужно знать базовый адрес массива или индекс внутри него, передача одного указателя, объединяющего два, будет дешевле, чем передача значений отдельно.
Хотя существует много разумных способов, которыми C мог бы кодировать длины строк, подходы, которые были изобретены до этого времени, имели бы все необходимые функции, которые должны были бы работать с частью строки, чтобы принимать базовый адрес строка и требуемый индекс как два отдельных параметра. Использование нулевого байтового завершения позволило избежать этого требования. Хотя другие подходы были бы лучше с современными машинами (современные компиляторы часто передают параметры в регистрах, и memcpy можно оптимизировать способами strcpy () - эквиваленты не могут), достаточно производственного кода, использующего строки с нулевым байтом, которые трудно изменить на что-либо другое.
PS - В обмен на небольшое снижение скорости при выполнении некоторых операций и незначительные дополнительные затраты на более длинные строки было бы возможно иметь методы, работающие со строками, принимающие указатели непосредственно на строки, bounds -checked строковые буферы или структуры данных, идентифицирующие подстроки другой строки. Функция типа "strcat" выглядела бы как [современный синтаксис]
void strcat(unsigned char *dest, unsigned char *src)
{
struct STRING_INFO d,s;
str_size_t copy_length;
get_string_info(&d, dest);
get_string_info(&s, src);
if (d.si_buff_size > d.si_length) // Destination is resizable buffer
{
copy_length = d.si_buff_size - d.si_length;
if (s.src_length < copy_length)
copy_length = s.src_length;
memcpy(d.buff + d.si_length, s.buff, copy_length);
d.si_length += copy_length;
update_string_length(&d);
}
}
Немного больше, чем метод K & R strcat, но он будет поддерживать проверку границ, чего нет у метода K & R. Кроме того, в отличие от текущего способа, можно было бы легко объединить произвольную подстроку, например
/* Concatenate 10th through 24th characters from src to dest */
void catpart(unsigned char *dest, unsigned char *src)
{
struct SUBSTRING_INFO *inf;
src = temp_substring(&inf, src, 10, 24);
strcat(dest, src);
}
Обратите внимание, что время жизни строки, возвращаемой temp_substring, будет ограничено значениями s
и src
, которые когда-либо были короче (именно поэтому метод требует, чтобы inf
передавался - если это было локальный, он умрет, когда метод вернется).
С точки зрения стоимости памяти строки и буферы длиной до 64 байт будут иметь один байт служебной информации (такой же, как строки с нулевым символом в конце); более длинные строки будут иметь немного больше (допустимо ли одно количество служебных данных между двумя байтами, а максимальный требуемый будет компромиссом времени / пространства). Специальное значение байта длины / режима будет использоваться для указания того, что строковой функции была дана структура, содержащая байт флага, указатель и длину буфера (которая затем может произвольно индексироваться в любую другую строку).
Конечно, K & R не реализовала ничего подобного, но, скорее всего, потому, что они не хотели тратить много усилий на обработку строк - область, где даже сегодня многие языки кажутся довольно анемичными.