Проблема хранения строк внутри двойного указателя - PullRequest
1 голос
/ 23 октября 2019

Я выполняю этот вызов кодовых войн , в котором вам нужно взять строку и использовать каждую букву с заглавной буквы, образуя мексиканский массив строк. Например, строка ввода, такая как

hello

, приведет к ["Hello", "hEllo", "heLlo", "helLo", "hellO"]

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

Это мой код:

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

void wave(char *s, char **array);

int main(void)
{
    char *s = malloc(6);
    strcpy(s, "hello");

    char **array = malloc(pow(strlen(s)+1, 2));

    wave(s, array);

    for (int i = 0; i < strlen(s); i++)
    {
        printf("s = %s\n", array[i]);
    }

    free(array);
    free(s);

    return 0;
}

void wave(char *s, char **array)
{
    char s2[strlen(s)+1];

    for (int i = 0; i < strlen(s); i++)
    {
        s[i] = tolower(s[i]);
    }

    int array_index = 0;

    for (int i = 0; i < strlen(s); i++)
    {
        strcpy(s2, s);

        if (s[i] != ' ')
        {

            s2[i] = toupper(s2[i]); // Printing out `s2` here results in the correct output
            array[array_index++] = s2; // Adding it here works, but when trying to access it outside of this function, it gives the incorrect output
        }
    }
}

При печати строки внутри функции я получаю следующий вывод (что правильно):

Hello
hEllo
heLlo
helLo
hellO

Но когда я пытаюсь распечатать ее внутри функции main(), яполучить следующее:

s = hellO
s = hellO
s = hellO
s = hellO
s = hellO

Кажется, что он добавляет / получает доступ только к последней строке в массиве. Я не могу понять, почему доступ к элементу внутри функции wave() работает, но доступ к нему вне этой функции не работает.

У меня уже была эта проблема дважды, как в C, так и C++. и не смогли ее решить, и это меня действительно раздражает.

Ответы [ 2 ]

2 голосов
/ 23 октября 2019

Для начала непонятно, почему вы динамически выделяете память для строкового литерала "привет".

char *s = malloc(6);
strcpy(s, "hello");

Это не имеет никакого смысла.

Просто напишите

const char *s = "hello";

Это объявление

char **array = malloc(pow(strlen(s), 2));

также не имеет смысла. Вам нужно следующее:

size_t n = strlen( s );

char **array = malloc( n * sizeof( char * ) );
for ( size_t i = 0; i < n; i++ )
{
    array[i] = malloc( n + 1 );
} 

Функция wave может быть определена следующим образом

void wave( const char *s, char **array )
{
    size_t n = strlen( s );

    for ( size_t i = 0; i < n; i++ )
    {
        strcpy( array[i], s );
        array[i][i] = toupper( ( unsigned char )s[i] );
    }
}

и затем в main после вызова функции

for ( size_t i = 0; i < n; i++ )
{
    puts( array[i] );
}

for ( size_t i = 0; i < n; i++ ) free( array[i] );
free( array );

Вот полная программа.

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

void wave( const char *s, char **array )
{
    size_t n = strlen( s );

    for ( size_t i = 0; i < n; i++ )
    {
        strcpy( array[i], s );
        array[i][i] = toupper( ( unsigned char )s[i] );
    }
}

int main(void) 
{
    const char *s = "hello";
    size_t n = strlen( s );

    char **array = malloc( n * sizeof( char * ) );

    for ( size_t i = 0; i < n; i++ )
    {
        array[i] = malloc( n + 1 );
    }

    wave( s, array );

    for ( size_t i = 0; i < n; i++ )
    {
        puts( array[i] );
    }

    for ( size_t i = 0; i < n; i++ ) free( array[i] );
    free( array );

    return 0;
}

Его вывод

Hello
hEllo
heLlo
helLo
hellO
2 голосов
/ 23 октября 2019

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

Как указано в моем комментарии, нет смысла выделять массив указателей - on64-битная машина, это будет 6 указателей, каждый из которых требует 8 байтов для указания на 7-байтовый блок данных - всего 104 байта (игнорируя добавленное заполнение распределителя для каждого выделения).

Вместо этого может быть достаточно одного выделения, выделяя 42 байта для хранения всех "волновых" строк и их байта NUL в одном блоке памяти (сохранение памяти при улучшении локальности).

int main(void) {
  /* Assuming string "hello" */
  const char *org = "hello";
  /* Calculate length only once and store value */
  const size_t len = strlen(org);
  const size_t len_with_nul = len + 1;
  /* Allocate `len` strings in a single allocation */
  char *buf = malloc(len * len_with_nul);
  /* Copy each string to it's place in the buffer */
  for (size_t i = 0; i < len; ++i) {
    /* position in the buffer */
    char *pos = buf + (i * len_with_nul);
    /* copy the NUL as well */
    memcpy(pos, org, len_with_nul); 
    /* Wave... */
    pos[i] = toupper(pos[i]);
  }
  /* Print result */
  for (size_t i = 0; i < len; i++) {
    char *pos = buf + (i * len_with_nul);
    printf("s = %s\n", pos);
  }
  /* Free buffer */
  free(buf);
  return 0;
}

РЕДАКТИРОВАТЬ - Почему лучше использовать один блок памяти? :

В этом случае мы выделяем один «блок» памяти (blob / slice). Это дает ряд преимуществ:

  • Мы выполняем одно выделение и освобождение вместо большего количества выделений и освобождений.

    Это повышает скорость за счет выполнения меньшего количества действий.

  • Мы также улучшаем локальность памяти, , которая минимизирует пропуски кэша ЦП и повышает производительность .

  • Мы используем меньше памяти.

    Каждое выделение памяти имеет свою цену - нам нужен указатель для хранения адреса памяти для выделенной памяти. Указатель «стоит» 8 байтов на 64-битной машине и 4 байта на 32-битной машине.

    Используя одно выделение, мы «платим» меньше.

    Это верно, даже если мы игнорируемметаданные, прикрепленные к выделенному блоку памяти (для которого требуется память из распределителя памяти).

Следует отметить, что C на самом деле не заботится о содержимом блока памяти, это все нули и единицы . Значение, данное этим нулям и единицам, оставлено на усмотрение разработчика.

Даже функция printf не заботится о содержимом памяти, которую она читает - она ​​просто читает память в соответствии с форматированием, в котором она находиласьразработчик должен следовать инструкциям (%s сообщает функции, что память связана со строкой, заканчивающейся NUL).

Существуют некоторые проблемы с выравниванием памяти, которые зависят от процессора и системы, но это не такприменять к однобайтовым строкам. Они применяются к многобайтовым типам (таким как short, int и long). Поэтому нам не нужно беспокоиться о них в этом примере.

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

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

...