Соединить строки в C с разделителем - PullRequest
0 голосов
/ 30 сентября 2019

Я пытаюсь написать функцию для объединения строк с разделителем. Вот что у меня есть:

int main(void) {

    char * strings[] = {"A", "B", NULL};
    char ** copied_strings = malloc(sizeof strings);

    // Join strings with a separator
    char * separator = "XXX";
    size_t num_array_elements = (sizeof strings / sizeof * strings) - 1; // because last element is NULL
    size_t len_separator = strlen(separator);
    size_t len_strings = 0;
    for (int i=0; strings[i] != NULL ;i++) len_strings += strlen(strings[i]);
    size_t malloc_buffer_size = len_strings + (len_separator * (num_array_elements -1)) + 1;
    printf("Separator: %s | Len Array: %lu | Len Strings: %lu | Malloc Buffer Size: %lu\n", separator, num_array_elements, len_strings, malloc_buffer_size);
    char * joined_string_buffer = malloc(malloc_buffer_size);
    join_strings(joined_string_buffer, copied_strings, separator);

}

void join_strings(char * joined_string_buffer, char ** src, char * separator) {

    size_t sep_len = strlen(separator);

    while (*src) {
        size_t string_len = strlen(*src);
        for (int i=0; i<string_len; i++)
            *joined_string_buffer++ = (*src)[i];
        for (int i=0; i<sep_len; i++)
            *joined_string_buffer++ = separator[i];
        *src++;
    }

    *joined_string_buffer = '\0';

}

Однако, похоже, что я неправильно копирую символы в *joined_string_buffer. Как бы я правильно присоединился к струнам здесь?

Ответы [ 4 ]

1 голос
/ 30 сентября 2019

В коде много проблем, но они в основном детали. К сожалению, в программировании даже детали должны быть правильными. Этот код исправляет большинство проблем, большинство из которых были определены в комментариях.

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

static void join_strings(char *joined_string_buffer, char **src, char *separator);

int main(void)
{
    char *strings[] = { "A", "B", NULL };
    char *separator = "XXX";
    size_t num_array_elements = (sizeof strings / sizeof *strings) - 1;  // because last element is NULL
    size_t len_separator = strlen(separator);

    size_t len_strings = 0;
    for (int i = 0; strings[i] != NULL; i++)
        len_strings += strlen(strings[i]);

    size_t malloc_buffer_size = len_strings + (len_separator * (num_array_elements - 1)) + 1;
    printf("Separator: '%s' | Len Array: %zu | Len Strings: %zu | Malloc Buffer Size: %zu\n",
           separator, num_array_elements, len_strings, malloc_buffer_size);
    char *joined_string_buffer = malloc(malloc_buffer_size);
    if (joined_string_buffer == 0)
    {
        fprintf(stderr, "failed to allocate %zu bytes of memory\n", malloc_buffer_size);
        exit(EXIT_FAILURE);
    }

    join_strings(joined_string_buffer, strings, separator);

    printf("[[%s]]\n", joined_string_buffer);
    free(joined_string_buffer);
    return 0;
}

static void join_strings(char *joined_string_buffer, char **src, char *separator)
{
    size_t sep_len = strlen(separator);

    while (*src)
    {
        size_t string_len = strlen(*src);
        for (size_t i = 0; i < string_len; i++)
            *joined_string_buffer++ = (*src)[i];
        for (size_t i = 0; i < sep_len; i++)
            *joined_string_buffer++ = separator[i];
        src++;
    }

    *joined_string_buffer = '\0';
}

Пример вывода

Separator: 'XXX' | Len Array: 2 | Len Strings: 2 | Malloc Buffer Size: 6
[[AXXXBXXX]]

Обратите внимание, что разделитель - это более строго терминатор- появляется после последнего элемента в списке, а также между ними.

Показанный код является более или менее прямым исправлением кода в вопросе. Но разделение работы между кодом в main() и функцией join_strings() не является хорошим. Это лучшее разделение обязанностей:

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

static char *join_strings(char **src, char *separator);

int main(void)
{
    char *strings[] = { "A", "B", NULL };
    char *separator = "XXX";
    char *result = join_strings(strings, separator);

    printf("[[%s]]\n", result);
    free(result);

    return 0;
}

static char *join_strings(char **src, char *separator)
{
    size_t len_sep = strlen(separator);
    size_t num_str = 0;

    for (size_t i = 0; src[i] != NULL; i++)
        num_str++;

    size_t len_str = 0;
    for (int i = 0; src[i] != NULL; i++)
        len_str += strlen(src[i]);

    size_t buf_len = len_str + (len_sep * num_str) + 1;
    printf("Separator: '%s' | Len Array: %zu | Len Strings: %zu | Malloc Buffer Size: %zu\n",
           separator, num_str, len_str, buf_len);
    char *result = malloc(buf_len);
    if (result == 0)
    {
        fprintf(stderr, "failed to allocate %zu bytes of memory\n", buf_len);
        exit(EXIT_FAILURE);
    }

    char *dst = result;
    for (size_t i = 0; src[i] != NULL; i++)
    {
        char *str = src[i];
        for (size_t j = 0; str[j] != '\0'; j++)
            *dst++ = str[j];
        for (size_t i = 0; i < len_sep; i++)
            *dst++ = separator[i];
    }

    *dst = '\0';
    return result;
}

Выходные данные такие же, как и раньше - с той же бородавкой, что и раньше, с разделителем и терминатором.

1 голос
/ 30 сентября 2019
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void join_strings(char* joined_string_buffer, const char* src[], const char* separator);

int main(void) {

    const char* strings[] = { "A", "B", NULL };
    char** copied_strings = (char**) malloc(sizeof strings);

    // Join strings with a separator
    const char* separator = "XXX";
    size_t num_array_elements = (sizeof strings / sizeof * strings) - 1; // because last element is NULL
    size_t len_separator = strlen(separator);
    size_t len_strings = 0;
    for (int i = 0; strings[i] != NULL;i++) len_strings += strlen(strings[i]);
    size_t malloc_buffer_size = len_strings + (len_separator * (num_array_elements - 1)) + 1;
    printf("Separator: %s | Len Array: %lu | Len Strings: %lu | Malloc Buffer Size: %lu\n", separator, num_array_elements, len_strings, malloc_buffer_size);
    char* joined_string_buffer = (char*) malloc(malloc_buffer_size);

    join_strings(joined_string_buffer, strings, separator);

    // Result is AXXXBXXX
    printf("%s\n", joined_string_buffer);

}

void join_strings(char* joined_string_buffer, const char* src[], const char* separator) {

    size_t sep_len = strlen(separator);

    while (*src) {
        size_t string_len = strlen(*src);
        for (int i = 0; i < string_len; i++)
            *joined_string_buffer++ = (*src)[i];
        for (int i = 0; i < sep_len; i++)
            *joined_string_buffer++ = separator[i];
        *src++;
    }

    *joined_string_buffer = '\0';

}

Полагаю, вы допустили ошибку, выбрав второй аргумент 'join_strings'

0 голосов
/ 30 сентября 2019

Как правило, не стоит делать байты самостоятельно. Ваш код является свидетельством этого. Вот наиболее явные проблемы, которые я вижу:

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

  • Вызовы strlen() должны повторять всю строку аргумента, чтобы найти завершающий нольбайт. Затем ваш код повторяет ту же последовательность байтов снова, перезагружая данные в CPU. Один из этих проходов по входной строке можно было бы оптимизировать, но только вы, я не думаю, что есть достаточно умных компиляторов, которые могли бы выполнить эту оптимизацию.

  • Ваш *Реализация 1015 * излишне сложна. Сложность делает код трудным для размышления, а сложный для восприятия - источником ошибок.

  • Копирование входных строк в main() не имеет смысла. Это только увеличивает сложность, делает код трудным для рассуждения и привлекает ошибки. Тебе лучше без него.

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

void write_strings(FILE* stream, int count, char** src, char* separator) {
    for(int i = 0; i < count; i++) {
        fprintf(stream, "%s%s", src[i], separator);
    }
}

Три строки, один цикл, один простой вызов fprintf().

Эта функция может использоваться для записи некоторых вещейв stderr, например: write_strings(stderr, 2, (char*){"Streams", "Rock"}, ". ") выведет сообщение «Streams. Rock.» в поток ошибок.

Но как получить результат в виде строки? Просто. Используйте open_memstream():

char* join_strings(int count, char** src, char* separator) {
    char* result = NULL;
    size_t length = 0;
    FILE* stream = open_memstream(&result, &length);
    write_strings(stream, count, src, separator);
    fclose(stream);
    return result;
}

В этой функции даже нет цикла, в основном это просто вызов write_strings().

Возвращаемая строка автоматически выделяется open_memstream(), , что означает нулевую опасность переполнения буфера . Это само по себе должно быть достаточной причиной для использования. Это также упрощает использование этой версии join_strings(): вызывающей программе не нужно решать, сколько места выделить, она просто помещает строки и получает присоединенную строку. Именно то, что ему нужно.

Вы можете заметить, что я изменил сигнатуру функции, добавив в нее аргумент count. Это изменение не является обязательным, но мой опыт подсказывает, что это гораздо лучший выбор, чем завершенный список NULL: вы можете легко забыть добавить терминатор NULL, но вы не можете забыть предоставить обязательный параметр функции. А предварительный подсчет позволяет реализовать многие алгоритмы гораздо более простым способом. Поэтому я по умолчанию всегда ассоциирую массивы с соответствующим аргументом размера, даже если бы я мог избежать этого, если бы захотел.

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

int main(void) {
    char * strings[] = {"A", "B"};    //no terminator necessary
    size_t num_array_elements = sizeof strings / sizeof * strings;
    char * separator = "XXX";

    printf("Separator: %s | Len Array: %lu\n", separator, num_array_elements);
    char * joined_string_buffer = join_strings(num_array_elements, strings, separator);

    //Added by me: Print the result to stdout
    printf("Resulting string: \"%s\" (%d characters)\n", joined_string_buffer, strlen(joined_string_buffer));

    //Cleanup: Free the buffer allocated by `open_memstream()`
    free(joined_string_buffer);
}

Обратите внимание, что во всем коде нет единого вычисления размера(все три функции), и при этом нет единственного явного вызова malloc(). Существует один free(), но это все о необходимом управлении памятью, тяжелая работа выполняется стандартными библиотечными функциями.

0 голосов
/ 30 сентября 2019

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

Существует несколько способов приблизиться кпроблема, но, учитывая, что ваш массив strings имеет sentinel NULL, вы могли бы сделать что-то вроде:

char *joinstr (const char **s, const char *sep)
{
    char *joined = NULL;                /* pointer to joined string w/sep */
    size_t lensep = strlen (sep),       /* length of separator */
        sz = 0;                         /* current stored size */
    int first = 1;                      /* flag whether first term */

    while (*s) {                        /* for each string in s */
        size_t len = strlen (*s);
        /* allocate/reallocate joined */
        void *tmp = realloc (joined, sz + len + (first ? 0 : lensep) + 1);
        if (!tmp) {                     /* validate allocation */
            perror ("realloc-tmp");     /* handle error (adjust as req'd) */
            exit (EXIT_FAILURE);
        }
        joined = tmp;                   /* assign allocated block to joined */
        if (!first) {                   /* if not first string */
            strcpy (joined + sz, sep);  /* copy separator */
            sz += lensep;               /* update stored size */
        }
        strcpy (joined + sz, *s++);     /* copy string to joined */
        first = 0;                      /* unset first flag */
        sz += len;                      /* update stored size */
    }

    return joined;      /* return joined string */
}

Добавление короткого main() для проверки joinstrВыше функции, вы можете сделать:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
...    
int main (void) {

    const char *strings[] = {"A", "B", NULL},
        *sep = "XXX";
    char *joined = joinstr (strings, sep);  /* join strings */

    printf ("%s\n", joined);    /* output joined string */
    free (joined);              /* free memory */
}

Пример использования / Вывод

$ ./bin/joinwsep
AXXXB

Использование памяти / проверка ошибок

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

IОбязательно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердитьчто вы освобождаете всю выделенную память.

Для Linux valgrind является нормальным выбором. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через нее.

$ valgrind ./bin/joinwsep
==17127== Memcheck, a memory error detector
==17127== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==17127== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==17127== Command: ./bin/joinwsep
==17127==
AXXXB
==17127==
==17127== HEAP SUMMARY:
==17127==     in use at exit: 0 bytes in 0 blocks
==17127==   total heap usage: 2 allocs, 2 frees, 8 bytes allocated
==17127==
==17127== All heap blocks were freed -- no leaks are possible
==17127==
==17127== For counts of detected and suppressed errors, rerun with: -v
==17127== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Посмотрите вещии дайте мне знать, если у вас есть дополнительные вопросы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...