Это хорошая подстрока для C?

См. также C Tokenizer


Вот быстрый substr() для C, который я написал (да, инициализацию переменных нужно переместить в начало функции и т. д., но вы поняли идею)

Я видел много «умных» реализаций substr(), которые просты, один лайнер вызывает strncpy()!

Все они неверны (strncpy не гарантирует нулевое завершение, и поэтому вызов может НЕ создать правильную подстроку!)

Вот что-то может лучше?

Выведи жуков!

char* substr(const char* text, int nStartingPos, int nRun)
{
    char* emptyString = strdup(""); /* C'mon! This cannot fail */

    if(text == NULL) return emptyString;

    int textLen = strlen(text);

    --nStartingPos;

    if((nStartingPos < 0) || (nRun <= 0) || (textLen == 0) || (textLen < nStartingPos)) return emptyString;

    char* returnString = (char *)calloc((1 + nRun), sizeof(char));

    if(returnString == NULL) return emptyString;

    strncat(returnString, (nStartingPos + text), nRun);

    /* We do not need emptyString anymore from this point onwards */

    free(emptyString);
    emptyString = NULL;

    return returnString;
}


int main()
{
    const char *text = "-2--4--6-7-8-9-10-11-";

    char *p = substr(text, -1, 2);
    printf("[*]'%s' (\")\n",  ((p == NULL) ? "<NULL>" : p));
    free(p);

    p = substr(text, 1, 2);
    printf("[*]'%s' (-2)\n", ((p == NULL) ? "<NULL>" : p));
    free(p);

    p = substr(text, 3, 2);
    printf("[*]'%s' (--)\n", ((p == NULL) ? "<NULL>" : p));
    free(p);

    p = substr(text, 16, 2);
    printf("[*]'%s' (10)\n", ((p == NULL) ? "<NULL>" : p));
    free(p);

    p = substr(text, 16, 20);
    printf("[*]'%s' (10-11-)\n", ((p == NULL) ? "<NULL>" : p));
    free(p);

    p = substr(text, 100, 2);
    printf("[*]'%s' (\")\n", ((p == NULL) ? "<NULL>" : p));
    free(p);

    p = substr(text, 1, 0);
    printf("[*]'%s' (\")\n", ((p == NULL) ? "<NULL>" : p));
    free(p);

    return 0;
}

Выход :

[*]'' (")
[*]'-2' (-2)
[*]'--' (--)
[*]'10' (10)
[*]'10-11-' (10-11-)
[*]'' (")
[*]'' (")

person PoorLuzer    schedule 17.05.2009    source источник
comment
Пока мы это делаем, printf(%s\n, NULL) будет печатать (null)\n, а не вылетать из-за какой-то серьезной ошибки, поэтому вам не нужно проверять, является ли p NULL внутри вашего printf() с.   -  person Chris Lutz    schedule 17.05.2009
comment
На самом деле, я не могу найти ничего в каких-либо стандартах относительно поведения printf() при задании NULL в виде строки, но это то, что он делает в моей системе (OS X Leopard).   -  person Chris Lutz    schedule 17.05.2009
comment
Да, Крис, у меня тоже есть (null) на всех ОС, над которыми я работал, но затем strcmp() с ядром NULL на Solaris (или это был HP-UX?), но на других это не так.. так что зачем возиться с маленькой тройкой?   -  person PoorLuzer    schedule 17.05.2009
comment
Из C11: ... %s ... аргумент должен быть указателем на начальный элемент массива символьного типа. Таким образом, NULL недопустим согласно стандарту. Вы возитесь с этим маленьким тройником, если хотите, чтобы ваш код был переносимым :-)   -  person paxdiablo    schedule 18.04.2012
comment
@paxdiablo: Другими словами, я правильно написал код 2 года назад, и не слушать Криса было хорошей идеей :-D?   -  person PoorLuzer    schedule 18.04.2012


Ответы (5)


Я бы сказал, верните NULL, если ввод недействителен, а не пустую строку malloc()ed. Таким образом, вы можете проверить, не сработала ли функция, используя if(p), а не if(*p == 0).

Кроме того, я думаю, что ваша функция пропускает память, потому что emptyString — это только free()d в одном условном выражении. Вы должны убедиться, что вы free() безоговорочно, то есть прямо перед return.

Что касается вашего комментария о том, что strncpy() не завершает строку NUL (что верно), если вы используете calloc() для выделения строки, а не malloc(), это не будет проблемой, если вы выделяете на один байт больше, чем копируете, поскольку calloc() автоматически устанавливает все значения (включая, в данном случае, конец) в 0.

Я бы дал вам больше заметок, но я ненавижу читать код в CamelCase. Не то, чтобы с этим что-то не так.

РЕДАКТИРОВАТЬ: Что касается ваших обновлений:

Имейте в виду, что стандарт C определяет sizeof(char) равным 1 независимо от вашей системы. Если вы используете компьютер, который использует 9 бит в байте (не дай Бог), sizeof(char) все равно будет равно 1. Не то чтобы было что-то неправильное в том, чтобы сказать sizeof(char) — это ясно показывает ваше намерение и обеспечивает симметрию с вызовами calloc() или malloc(). для других типов. Но sizeof(int) на самом деле полезен (int может быть разного размера на 16- и 32- и этих новомодных 64-битных компьютерах). Чем больше ты знаешь.

Я также хотел бы повторить, что согласованность с большинством других кодов C состоит в том, чтобы возвращать NULL при ошибке, а не "". Я знаю, что многие функции (например, strcmp()), вероятно, будут делать плохие вещи, если вы передадите им NULL - этого и следовало ожидать. Но стандартная библиотека C (и многие другие API-интерфейсы C) используют подход «Вызывающий обязан проверять наличие NULL, а не обязанность функции заботиться о нем/нее, если он (она) этого не делает». Если вы хотите сделать это по-другому, это круто, но это противоречит одной из самых сильных тенденций в дизайне интерфейса C.

Кроме того, я бы использовал strncpy() (или memcpy()), а не strncat(). Использование strncat()strcat()) скрывает ваши намерения - это заставляет кого-то, глядя на ваш код, думать, что вы хотите добавить в конец строки (что вы и делаете, потому что после calloc() конец является началом), когда то, что вы хотите сделать устанавливается строка. strncat() создает впечатление, что вы добавляете к строке, в то время как strcpy() (или другая процедура копирования) делает это более похожим на ваше намерение. Следующие три строки делают одно и то же в этом контексте — выберите ту, которая, по вашему мнению, выглядит лучше всего:

strncat(returnString, text + nStartingPos, nRun);

strncpy(returnString, text + nStartingPos, nRun);

memcpy(returnString, text + nStartingPos, nRun);

Кроме того, strncpy() и memcpy(), вероятно, будут (чуть-чуть) немного быстрее/эффективнее, чем strncat().

text + nStartingPos совпадает с nStartingPos + text - я бы поставил char * первым, так как я думаю, что это понятнее, но в каком порядке вы хотите их расположить, зависит от вас. Кроме того, круглые скобки вокруг них не нужны (но приятны), поскольку + имеет более высокий приоритет, чем ,.

РЕДАКТИРОВАТЬ 2: три строки кода не делают одно и то же, но в этом контексте все они будут давать один и тот же результат. Спасибо, что поймали меня на этом.

person Chris Lutz    schedule 17.05.2009
comment
Ваше утверждение о том, что 3 строки кода одинаковы, неверно. strncat гарантированно добавляет суффикс 0 после копирования исходной строки. strncpy не гарантирует суффикс 0 после копирования, но он остановит процесс копирования, как только встретит первый \0.. .. и мы все знаем, что такое memcpy :-) - person PoorLuzer; 17.05.2009
comment
Они не одинаковы, но в данном контексте все они дадут один и тот же результат. Я хочу сказать, что strncat выполняет много дополнительной (и в данном случае ненужной) работы, чем strncpy или memcpy. - person Chris Lutz; 18.05.2009

Ваша функция кажется очень сложной для того, что должно быть простой операцией. Некоторые проблемы (не все из них являются ошибками):

  • strdup() и другие функции распределения памяти могут дать сбой, следует учесть все возможные проблемы.
  • выделяйте ресурсы (память в данном случае) только тогда, когда они вам нужны.
  • вы должны уметь различать ошибки и действительные укусы. На данный момент вы не знаете, создает ли malloc() сбой substr ("xxx",1,1) или работающий substr ("xxx",1,0) пустую строку.
  • вам не нужно calloc() память, которую вы все равно собираетесь перезаписать.
  • все недопустимые параметры должны либо вызывать ошибку, либо быть приведены к действительному параметру (и ваш API должен задокументировать это).
  • вам не нужно устанавливать локальную emptyString в NULL после ее освобождения - она ​​будет потеряна при возврате функции.
  • вам не нужно использовать strncat() - вы должны знать размеры и объем доступной памяти, прежде чем делать какое-либо копирование, чтобы вы могли использовать (скорее всего) более быстрый memcpy().
  • вы используете base-1, а не base-0 для смещений строк, что противоречит структуре C.

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

char *substr (const char *inpStr, int startPos, int strLen) {
    /* Cannot do anything with NULL. */

    if (inpStr == NULL) return NULL;

    /* All negative positions to go from end, and cannot
       start before start of string, force to start. */

    if (startPos < 0)
        startPos = strlen (inpStr) + startPos;
    if (startPos < 0)
        startPos = 0;

    /* Force negative lengths to zero and cannot
       start after end of string, force to end. */

    if (strLen < 0)
        strLen = 0;
    if (startPos >strlen (inpStr))
        startPos = strlen (inpStr);

    /* Adjust length if source string too short. */

    if (strLen > strlen (&inpStr[startPos]))
        strLen = strlen (&inpStr[startPos]);

    /* Get long enough string from heap, return NULL if no go. */

    if ((buff = malloc (strLen + 1)) == NULL)
        return NULL;

    /* Transfer string section and return it. */

    memcpy (buff, &(inpStr[startPos]), strLen);
    buff[strLen] = '\0';

    return buff;
}
person paxdiablo    schedule 17.05.2009

char* emptyString = strdup(""); /* C'mon! This cannot fail? */

Вам нужно проверить на ноль. Помните, что для нулевого символа по-прежнему должен быть выделен 1 байт.

person rlbond    schedule 17.05.2009

strdup может выйти из строя (хотя это очень маловероятно и не стоит проверять, ИМХО). Однако у него есть еще одна проблема - это не стандартная функция C. Лучше использовать malloc.

person Community    schedule 17.05.2009
comment
Для чего бы это ни стоило, strdup() достаточно легко написать, что вы могли бы также использовать его и использовать autoconf или что-то подобное, чтобы проверить, нужно ли вам свернуть свою собственную версию. - person Chris Lutz; 17.05.2009
comment
strdup теперь удален .. люди были слишком педантичны ;-) - person PoorLuzer; 17.05.2009

Вы также можете использовать функцию memmove для возврата подстроки от начала до длины. Улучшение/добавление другого решения из решения paxdiablo:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>

    char *splitstr(char *idata, int start, int slen) {
            char ret[150];
            if(slen == NULL) {
                    slen=strlen(idata)-start;
            }
            memmove (ret,idata+start,slen);
            return ret;
    }

    /*
    Usage:
            char ostr[]="Hello World!";
            char *ores=splitstr(ostr, 0, 5);
            Outputs:
                    Hello
    */

Надеюсь, поможет. Протестировано на Windows 7 Home Premium с компилятором TCC C.

person dsrdakota    schedule 01.08.2012