Манипуляции со строками и распределение памяти - C - PullRequest
8 голосов
/ 07 июля 2010

Я нахожусь в процессе изучения C. У меня есть метод, который берет 3 строки и объединяет их для выполнения некоторой операции. Ниже была моя первая реализация с использованием компилятора GCC.

void foo(const char *p1, const char *p2, const char *p3)
{
    size_t length = strlen(p1) + strlen(p2) + strlen(p3);
    char combined[length + 1];
    memset(combined, 0, length + 1);
    strcat(combined, p1);
    strcat(combined, p2);
    strcat(combined, p3);
    printf("Result : %s", combined);
}

int main()
{
    foo("hello ", "world ", "how");
    return 0;
}

Это хорошо работает. Но когда я скомпилировал это, используя cc -Wall -pedantic -g foo.c -o foo, я начал получать предупреждения типа ISO C90 forbids variable length array ‘combined’. MSVC не компилировал этот код. Изменен код как

void foo(const char *p1, const char *p2, const char *p3)
{
    size_t length = strlen(p1) + strlen(p2) + strlen(p3);
    char *combined = (char *) malloc(length + 1);
    memset(combined, 0, length + 1);
    strcat(combined, p1);
    strcat(combined, p2);
    strcat(combined, p3);
    printf("Result : %s", combined);
    free(combined);
}

Вопросы

  1. Это правильная реализация?
  2. Если массивы переменной длины не являются частью стандарта, почему GCC реализовал это? Если ожидается, что код будет компилироваться только в GCC, использование переменных-массивов будет лучшей альтернативой, чем использование malloc?
  3. Я думаю, что правило большого пальца гласит: если требуемая память известна во время компиляции, используйте массивы, иначе используйте malloc для выделения необходимой памяти. Это правильно?
  4. Мой код должен компилироваться в GCC и MSVC. Я буду разрабатывать на GCC обычно. Так какие же флаги компилятора обеспечивают максимальную переносимость? В настоящее время я использую -Wall -pedantic. Должен ли я использовать -ansi тоже? Какие бы эквивалентные флаги были доступны в MSVC?
  5. Что еще нужно учитывать при написании переносимого кода на C?

Ответы [ 4 ]

8 голосов
/ 07 июля 2010

Это хорошо работает.Но когда я скомпилировал это с помощью cc -Wall -pedantic -g foo.c -o foo, я начал получать предупреждения, такие как ISO C90 запрещает массив переменной длины «объединяться».

Попробуйте скомпилировать с опцией -std=c99 (gcc).

MSVC не компилировал этот код.Изменен код как

Если массивы переменной длины не являются частью стандарта, почему GCC реализовал это?

VLA являются частью поддержки ISO C99 (gcc и g ++ (как расширение)Влас).MSVC по-прежнему поддерживает только C89.

Ожидается, что мой код скомпилируется на GCC и MSVC.

Тогда вам не следует использовать VLA в вашем коде ИМХО.

7 голосов
/ 07 июля 2010
  1. Да, это так.Там нет никаких конкретных нарушений стандарта.Однако memset - пустая трата времени, так как в любом случае его все равно перезаписывают (превратите свой первый strcat в strcpy).И вы должны всегда проверить, чтобы malloc вернул NULL. Неважно, что!
  2. C89 / 90 - это не текущий стандарт, а C99.И C1x не так уж далеко.GCC идет в ногу с кровоточащим фронтом.
  3. Используйте только локальные массивы, если они вам не нужны, чтобы выжить после окончания функции.В противном случае malloc - ваша лучшая ставка, особенно если вы хотите вернуть комбинированную строку.
  4. Я думаю, что у gcc есть флаг -std=c89 или что-то подобное.В любом случае MSVC не всегда следует стандарту: -)
  5. Часто компилируйте и тестируйте его на обеих платформах.Это единственный способ быть уверенным.

Я бы выбрал:

void foo (const char *p1, const char *p2, const char *p3) {
    size_t length = strlen(p1) + strlen(p2) + strlen(p3);
    char *combined = (char *) malloc(length + 1);
    if (combined == NULL) {
        printf("Result : <unknown since I could't get any memory>\n");
    } else {
        strcpy(combined, p1);
        strcat(combined, p2);
        strcat(combined, p3);
        printf("Result : %s", combined);
        free(combined);
    }
}

или, поскольку вы на самом деле ничего не делаете со строкой, кроме ее печати:

void foo (const char *p1, const char *p2, const char *p3) {
    printf("Result : %s%s%s", p1, p2, p3);
}

: -)

Другая стратегия, которую я видел, - это стратегия «только выделяй, если нужно»:

void foo (const char *p1, const char *p2, const char *p3) {
    char str1k[1024];
    char *combined;
    size_t length = strlen (p1) + strlen (p2) + strlen (p3) + 1;
    if (length <= sizeof(str1k))
        combined = str1k;
    else
        combined = malloc (length);
    if (combined == NULL) {
        printf ("Result : <unknown since I couldn't get any memory>\n");
    } else {
        strcpy (combined, p1);
        strcat (combined, p2);
        strcat (combined, p3);
        printf ("Result : %s", combined);
    }
    if (combined != str1k)
        free (combined);
}

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

3 голосов
/ 07 июля 2010

Массивы переменной длины не были частью первого стандарта ISO C (по-разному именуемого как «C89», «C90» или «ANSI C»).Однако они являются частью последнего стандарта ISO C (известного как "C99").

GCC может компилировать ваш код в нескольких режимах, включая "строгий C90", "C90-with-GNU-C-extensions "и" C99 "(хотя он не полностью реализует C99, он достаточно близок для большинства практических целей).

По умолчанию GCC использует"C90-with-GNU-C-extensions ", поэтому ваш код компилируется без жалоб.Использование -pedantic предписывает ему выдавать все необходимые предупреждения согласно соответствующему стандарту (в данном случае, C90), и такое предупреждение требуется вашим кодом.Если вы дадите GCC флаги -std=c99 -pedantic, чтобы заставить его компилироваться в соответствии с базовым стандартом C99 и выдавать все необходимые предупреждения, ваш код компилируется нормально.

Если вы хотите убедиться, что ваш код совместим с базовым C90стандартный, затем используйте -std=c90 -pedantic (или -ansi -pedantic: -ansi является синонимом -std=c90 при компиляции кода C).Обратите внимание, что MSVC не поддерживает C99.

0 голосов
/ 07 июля 2010

Очень распространенная идиома для решения этих проблем - позволить вызывающей стороне управлять памятью. Поэтому вместо того, чтобы выделять память самостоятельно (используя массив переменной длины в стеке или malloc что-то, или что-то еще), вы ожидаете, что вызывающая сторона предоставит память. Учтите это:

int foo(const char *p1, const char *p2, const char *p3, char *buf, size_t bufsize)
{
    size_t requiredSize = strlen(p1) + strlen(p2) + strlen(p3) + 1;
    if (!buf)
        return requiredSize;
    if (requiredSize > bufsize)
        return -1;
    buf[0] = '\0';
    strcat(buf, p1);
    strcat(buf, p2);
    strcat(buf, p3);
    return requiredSize;
}

int main()
{
  /* simple case: caller knows that the buffer is large enough. */
  char buf[ 1024 ];
  foo( "Hello", "World", "Bar", buf, sizeof(buf) );
  printf("Result : %s\n", buf);

  /* complicated case: caller wants to allocate buffer of just the right size */
  size_t bufsize = foo( "Hello", "World", "Bar", NULL, 0 );
  char *buf2 = (char *)malloc(bufsize);
  foo( "Hello", "World", "Bar", buf2, bufsize );
  free( buf2 );
}

Преимущество этого подхода в том, что foo никогда не протечет. Кроме того, вызывающая сторона может использовать простой массив на основе стека в случае, если он работает для него. Если он хочет знать точный размер, он может позвонить foo и передать NULL в качестве четвертого аргумента.

...