Строки в C: подводные камни и техника

В следующем месяце я буду тренировать команду ACM (перейти к рисунку), и пришло время поговорить о строках в C. Кроме обсуждения стандартных lib, strcpy, strcmp и т.д., я бы чтобы дать им некоторые подсказки (что-то вроде str[0] is equivalent to *str и тому подобное).

Знаете ли вы какие-либо списки (например, чит-листы) или свой собственный опыт в этом вопросе?

Я уже знаю книги для конкурса ACM (которые хороши, см., в частности, это), но я после трюки торговли.

Спасибо.

Изменить: Спасибо всем. Я согласен с большинством проголосовавших ответов и должным образом поддержал других, которые, на мой взгляд, актуальны. Я ожидаю сделать здесь резюме (например, здесь, как можно скорее). Сейчас у меня достаточно материала, и я уверен, что это значительно улучшило сеанс на строках. Еще раз спасибо.

16 ответов

Это очевидно, но я думаю, важно знать, что строки nothing больше, чем массив байтов, разделенных нулевым байтом. Строки C не так уж удобны для пользователя, как вы, вероятно, знаете.

  • Запись нулевого байта где-то в строке будет обрезать его.
  • Выход из пределов вообще заканчивается плохо.
  • Никогда, никогда не используйте strcpy, strcmp, strcat и т.д., вместо этого используйте их безопасные варианты: strncmp, strncat, strndup,...
  • Избегайте strncpy. strncpy не всегда будет нулевой разграничить вашу строку! Если исходная строка не подходит в целевом буфере, она обрезает строку, но она не будет писать нулевой байт в конце буфера. Кроме того, даже если исходный буфер намного меньше места назначения, strncpy все равно перезапишет весь буфер нулями. Я лично использую strlcpy.
  • Не используйте printf (string), вместо этого используйте printf ( "% s", string). Попробуйте подумать о последствиях, если пользователь помещает% d в строку.
  • Вы не можете сравнивать строки с
    if( s1 == s2 )
     doStuff(s1);
    Вы должны сравнить каждый символ в строке. Используйте strcmp или лучше strncmp.
    if( strncmp( s1, s2, BUFFER_SIZE ) == 0 )
     doStuff(s1);


Нарушение strlen() значительно ухудшит производительность.

for( int i = 0; i < strlen( string ); i++ ) {
 processChar( string[i] );
}

будет иметь по меньшей мере O (n 2) временную сложность, тогда как

int length = strlen( string );
for( int i = 0; i < length; i++ ) {
 processChar( string[i] );
}

будет иметь как минимум O (n) временную сложность. Это не так очевидно для людей, которые не успели подумать об этом.


Варианты str n * в stdlib необязательно нулевые завершают строку назначения.

В качестве примера: из документации MSDN на strncpy:

Функция strncpy копирует начальные символы счета strSource to strDest и возвращает strDest. Если количество меньше или равно длина strSource, нулевой символ автоматически не добавляется к скопированная строка. Если счет больше чем длина strSource, строка назначения заполняется нулевой символов до длины.


str[0] эквивалентен 0[str], или более общ str[i] - i[str], а i[str] - *(str + i).

Н.Б.

это не относится к строкам, но работает также для массивов C


Следующие функции могут использоваться для реализации не мутирующего strtok:

strcspn(string, delimiters)
strspn(string, delimiters)

Первый находит первый символ в наборе разделителей, в который вы проходите. Второй находит первый символ не в наборе разделителей, которые вы передаете.

Я предпочитаю их strpbrk, поскольку они возвращают длину строки, если они не могут совпадать.


kmm уже имеет хороший список. Вот с чем я столкнулся, когда начал писать код C.

  • Строковые литералы имеют собственную секцию памяти и всегда доступны. Следовательно, они могут быть, например, возвращаемым значением функции.

  • Управление памятью строк, в частности, с библиотекой высокого уровня (не libc). Кто отвечает за освобождение строки, если она возвращается функцией или передается функции?

  • Когда должно быть "const char *" и когда используется "char *". И что это говорит мне, если функция возвращает "const char *".

Все эти вопросы не так уж трудно узнать, но трудно понять, не научитесь их.


strtok не является потокобезопасным, поскольку он использует изменяемый частный буфер для хранения данных между вызовами; вы также не можете чередовать или аннулировать вызовы strtok.

Более полезной альтернативой является strtok_r, использовать ее всякий раз, когда вы можете.


путать strlen() с sizeof() при использовании строки:

char *p = "hello!!";
strlen(p) != sizeof(p)

sizeof(p) дает во время компиляции размер указателя (4 или 8 байтов), тогда как strlen(p) подсчитывает во время выполнения длину массива char с нулевым завершением (7 в этом примере).


возможно, вы могли бы проиллюстрировать значение sentinel '\ 0' со следующим примером

char * a = "hello\0 world"; char b [100]; зЬгср (Ь, а); Е (б);

У меня когда-то мои пальцы горели, когда я в своем рвении использовал stcpy() для копирования двоичных данных. Он работал большую часть времени, но иногда таинственно. Тайна была обнаружена, когда я понял, что двоичный вход иногда содержит нулевой байт, и strcpy() завершается там.


Я обнаружил, что метод char buff[0] был невероятно полезен. Рассмотрим:

struct foo {
 int x;
 char * payload;
};

против

struct foo {
 int x;
 char payload[0];
};

см. qaru.site/questions/57335/...

См. ссылку на последствия и варианты


Я бы обсуждал, когда и когда не использовать strcpy и strncpy, и что может пойти не так:

char *strncpy(char* destination, const char* source, size_t n);
char *strcpy(char* destination, const char* source );

Я бы также упомянул возвращаемые значения строковых функций ansi C stdlib. Например, спросите: "делает ли это, если инструкция проходит или терпит неудачу?"

if (stricmp("StrInG 1", "string 1")==0)
{
 .
 .
 .
}


Указатели и массивы, имеющие аналогичный синтаксис, совсем не совпадают. Дано:

char a [100]; char * p = a;

Для массива a нет указателя, хранящегося в любом месте. sizeof (a)!= sizeof (p), для массива - размер блока памяти, для указателя - размер указателя. Это становится важным, если вы используете что-то вроде: sizeof (a)/sizeof (a [0]). Кроме того, вы не можете ++ a, и вы можете сделать указатель "const" указателем на символы "const" , но массив может быть только символами "const" , и в этом случае вы должны сначала его инициализировать. etc etc etc


Вы можете указать индексированную адресацию.

Адрес элементов - это базовый адрес + индекс * размер элемента


Если возможно, используйте strlcpy (вместо strncpy) и strlcat.

Еще лучше, чтобы сделать жизнь более безопасной, вы можете использовать макрос, например:

#define strlcpy_sz(dst, src) (strlcpy(dst, src, sizeof(dst)))


Общей ошибкой является:

char *p;
snprintf(p, 3, "%d", 42);

он работает до тех пор, пока вы не будете использовать до sizeof(p) байт. Затем происходят забавные вещи (добро пожаловать в джунгли).

Explaination

с char * p вы выделяете пространство для удержания указателя (sizeof(void*) bytes) в стеке. Правильная вещь здесь - выделить буфер или просто указать размер указателя во время компиляции:

char buf[12];
char *p = buf;
snprintf(p, sizeof(buf), "%d", 42);


Я бы указал на недостатки производительности чрезмерной зависимости от встроенных строковых функций.

char* triple(char* source)
{
 int n=strlen(source);
 char* dest=malloc(n*3+1);
 strcpy(dest,src);
 strcat(dest,src);
 strcat(dest,src);
 return dest;
 }

licensed under cc by-sa 3.0 with attribution.