Я читал тему под названием «strlen vs sizeof» на CodeGuru , и в одном из ответов говорится, что «в любом случае [так] плохая практика - инициализировать [sic] char
массив со строковым литералом».
Это правда или это только его (хотя и "элитный член") мнение?
Вот оригинальный вопрос:
#include <stdio.h>
#include<string.h>
main()
{
char string[] = "october";
strcpy(string, "september");
printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
return 0;
}
правильно. размер должен быть длина плюс 1 да?
это выход
the size of september is 8 and the length is 9
размер должен быть 10 обязательно. это похоже на вычисление размера строки до того, как она будет изменена на strcpy, но на длину после.
Что-то не так с моим синтаксисом или как?
Вот ответ :
В любом случае, плохо инициализировать массив символов строковым литералом. Поэтому всегда делайте одно из следующего:
const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
c
programming-practices
strings
array
Коул Джонсон
источник
источник
Ответы:
Автор этого комментария никогда не оправдывает его, и я нахожу это утверждение загадочным.
В C (и вы пометили это как C), это практически единственный способ инициализировать массив
char
со строковым значением (инициализация отличается от присваивания). Вы можете написать либоили
или
В первом случае размер массива берется из размера инициализатора. Строковые литералы хранятся в виде массивов
char
с завершающим 0 байтом, поэтому размер массива равен 8 ('o', 'c', 't', 'o', 'b', 'e', 'r', 0). Во вторых двух случаях размер массива указывается как часть объявления (8 иMAX_MONTH_LENGTH
, что бы это ни было).Что вы не можете сделать, это написать что-то вроде
или
и т. д. В первом случае объявление
string
является неполным, поскольку не указан размер массива и нет инициализатора, из которого можно получить размер. В обоих случаях=
не будет работать, потому что a) выражение массива, такое как,string
может не быть целью назначения и b)=
оператор не определен для копирования содержимого одного массива в другой в любом случае.К тому же, вы не можете написать
где
foo
другой массивchar
. Эта форма инициализации будет работать только со строковыми литералами.РЕДАКТИРОВАТЬ
Я должен изменить это, чтобы сказать, что вы также можете инициализировать массивы для хранения строки с инициализатором в стиле массива, например
или
но на глазах проще использовать строковые литералы.
РЕДАКТИРОВАТЬ 2
Чтобы назначить содержимое массива вне объявления, вам необходимо использовать либо
strcpy/strncpy
(для строк с нулем в конце), либоmemcpy
(для любого другого типа массива):или
источник
strncpy
редко бывает правильный ответchar[8] str = "october";
это плохая практика. Мне нужно было буквально считать себя, чтобы убедиться, что это не переполнение и оно не работает при обслуживании ... например, исправление орфографической ошибки отseprate
доseparate
сломается, если размер не обновляется.strlen()
не содержит нулевого символа, использованиеMAX_MONTH_LENGTH
для удержания максимального размера, необходимого дляchar string[]
часто, выглядит неправильно. IMO, здесьMAX_MONTH_SIZE
было бы лучше.Единственная проблема, которую я помню, это присвоение строкового литерала
char *
:Например, возьмите эту программу:
На моей платформе (Linux) происходит сбой при попытке записи на страницу, помеченную как доступную только для чтения. На других платформах может отображаться «сентябрь» и т. Д.
Тем не менее, инициализация литералом делает конкретную сумму резервирования, так что это не будет работать:
Но это будет
В качестве последнего замечания - я бы не стал использовать
strcpy
:Хотя некоторые компиляторы могут преобразовать его в безопасный вызов
strncpy
, гораздо безопаснее:источник
strncpy
поскольку он не завершает копируемую строку, если длинаsomething_else
больше чемsizeof(buf)
. Я обычно устанавливаю последний символbuf[sizeof(buf)-1] = 0
для защиты от этого или, еслиbuf
инициализируется нулями, используюsizeof(buf) - 1
в качестве длины копии.strlcpy
илиstrcpy_s
или дажеsnprintf
если вам нужно.strlcpy
иsnprintf
не доступны напрямую на MSVC, по крайней мере, заказы иstrcpy_s
не на * nix).Одна вещь, которую ни один поток не поднимает, состоит в следующем:
против
Первый будет делать что-то вроде:
Последний делает только memcpy. Стандарт C настаивает на том, что если какая-либо часть массива инициализируется, все это так. Так что в этом случае лучше сделать это самостоятельно. Я думаю, что это, возможно, было то, к чему добиралась treuss.
Точно
лучше чем либо:
или
ps Для получения бонусных баллов вы можете сделать:
бросить время компиляции делить на ноль ошибок, если вы собираетесь переполнить массив.
источник
Прежде всего потому, что у вас не будет размера
char[]
переменной / конструкции, которую вы могли бы легко использовать в программе.Пример кода по ссылке:
string
размещается в стеке длиной 7 или 8 символов. Я не могу вспомнить, завершен ли он таким образом или нет - нить, на которую вы ссылались, заявила, что это так.Копирование «сентябрь» поверх этой строки является очевидным переполнением памяти.
Другая проблема возникает, если вы переходите
string
к другой функции, чтобы другая функция могла писать в массив. Вы должны указать другой функции, как долго будет работать массив, чтобы он не создавал переполнение. Вы можете передатьstring
результат,strlen()
но поток объясняет, как это может взорваться, еслиstring
не завершено нулем.Вам лучше выделить строку с фиксированным размером (предпочтительно определяемым как константа), а затем передать массив и фиксированный размер другой функции. Комментарии @John Bode верны, и есть способы уменьшить эти риски. Они также требуют больше усилий с вашей стороны, чтобы их использовать.
По моему опыту, значение, которое я инициализировал
char[]
, обычно слишком мало для других значений, которые мне нужно поместить в него. Использование определенной константы помогает избежать этой проблемы.sizeof string
даст вам размер буфера (8 байт); используйте результат этого выражения вместо того,strlen
когда вы беспокоитесь о памяти.Кроме того , вы можете сделать проверку перед вызовом ,
strcpy
чтобы увидеть , если ваш целевой буфер достаточно большой для исходной строки:if (sizeof target > strlen(src)) { strcpy (target, src); }
.Да, если у вас есть , чтобы передать массив в функцию, вы должны будете пройти его физический размер , а также:
foo (array, sizeof array / sizeof *array);
. - Джон Бодеисточник
sizeof string
даст вам размер буфера (8 байт); используйте результат этого выражения вместо того,strlen
когда вы беспокоитесь о памяти. Кроме того , вы можете сделать проверку перед вызовом ,strcpy
чтобы увидеть , если ваш целевой буфер достаточно большой для исходной строки:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Да, если у вас есть , чтобы передать массив в функцию, вы должны будете пройти его физический размер , а также:foo (array, sizeof array / sizeof *array);
.string
приводят к неявному преобразованию вchar*
, указывая на первый элемент массива. Это теряет информацию о границах массива. Вызов функции - это только один из многих контекстов, в которых это происходит.char *ptr = string;
Другой. Дажеstring[0]
является примером этого;[]
оператор работает на указатели, а не непосредственно на массивах. Рекомендуемая литература: Раздел 6 comp.lang.c FAQ .Я думаю, что идея «плохой практики» исходит из того, что эта форма:
делает неявно strcpy из исходного машинного кода в стек.
Более эффективно обрабатывать только ссылку на эту строку. Как с:
или напрямую:
(но, конечно, в большинстве кода это, вероятно, не имеет значения)
источник
char time_buf[] = "00:00";
когда вы собираетесь изменять буфер?char *
Инициализируется строковый литерал устанавливается в адрес первого байта, поэтому пытается изменить это приводит к неопределенному поведению , так как метод хранения строкового литерала неизвестна (определяется реализацией), в то время как изменения в байтахchar[]
является совершенно законным , потому что инициализация копирует байты в пространство для записи, выделенное в стеке. Сказать, что это «менее эффективный» или «плохая практика» без уточнения нюансов,char* vs char[]
вводит в заблуждение.Никогда не бывает очень долго, но вам следует избегать инициализации char [] в string, потому что «string» - это const char *, и вы назначаете его в char *. Так что, если вы передадите этот char [] методу, который изменяет данные, у вас может быть интересное поведение.
Как сказал коммент, я смешал немного char [] с char *, что не очень хорошо, так как они немного отличаются.
Нет ничего плохого в назначении данных массиву char, но поскольку целью использования этого массива является использование его как 'string' (char *), легко забыть, что вы не должны изменять этот массив.
источник
const
будет, если вы не определите его таким образом. (И строковые литералы в C не являются таковымиconst
, хотя любая попытка изменить строковый литерал имеет неопределенное поведение.)char *s = "literal";
Есть поведение, о котором вы говорите; это лучше написать какconst char *s = "literal";
const
включенияint n = 42;
, потому что42
это константа.c
можно изменять. Это точно такая же сильная гарантия, как и та, которая1 + 1
дает оценку2
. Если программа, на которую я ссылался выше, делает что-то кроме печатиEFGH
, это указывает на несоответствующую реализацию языка Си.