C 在双指针中存储字符串时出现问题

C 在双指针中存储字符串时出现问题,c,pointers,memory-management,c-strings,C,Pointers,Memory Management,C Strings,我一直在这样做,你必须拿一个字符串,把每个字母大写,形成一个墨西哥波浪形的字符串数组。例如,输入字符串 你好 将导致[你好,你好,你好,你好,你好] 我设法用JavaScript完成了它,并决定用C语言尝试。实际的代码本身正在工作,因为它打印正确的输出,但我遇到的问题实际上是将字符串存储在一个双指针中 这是我的代码: #include <ctype.h> #include <math.h> #include <stdlib.h> #include <st

我一直在这样做,你必须拿一个字符串,把每个字母大写,形成一个墨西哥波浪形的字符串数组。例如,输入字符串

你好

将导致[你好,你好,你好,你好,你好]

我设法用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
        }
    }
}
但是,当我尝试在主函数中打印它时,我得到以下结果:

s = hellO
s = hellO
s = hellO
s = hellO
s = hellO
它似乎只添加/访问数组中的最后一个字符串。我不明白为什么访问波函数内部的元素是有效的,但是访问波函数外部的元素却不行


我以前在C和C++两次都遇到过这个问题,而且一直无法解决它,这真的让我很烦。

< P>我把它当作一个注释,但是因为它可能不清楚,所以我会把我的答案用代码…… 如我在评论中所述,分配指针数组没有意义——在64位机器上,这将是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。这有许多优点:

我们只执行一次分配和解除分配,而不是执行更多的分配和解除分配

这通过执行较少的操作来提高速度

我们还改进了内存局部性

我们使用更少的内存

每个内存分配都有一个价格-我们需要一个指针来保存我们分配的内存的内存地址。一个指针在64位机器上花费8字节,在32位机器上花费4字节

通过使用单一分配,我们支付的费用更少

即使我们忽略附加到已分配内存块的元数据(该内存块需要来自内存分配器的内存),这也是正确的

我应该注意到,C实际上并不关心内存块的内容,它都是0和1。这些0和1的含义留给开发人员

甚至printf函数也不关心它正在读取的内存的内容-它只是根据开发人员指示它遵循的格式读取内存%s通知函数内存与NUL终止的字符串有关

内存对齐是CPU和系统特有的,但这些不适用于单字节字符串。它们适用于多字节类型,如short、int和long。所以在这个例子中,我们不需要担心它们

从这个意义上讲,它基本上意味着开发人员可以自由地管理内存和内容,因为他们认为合适,而不必考虑内存对齐


这并不是说如果您需要使用realloc,分配一块内存总是更好,您可能更喜欢较小的内存块。。。但是通常一块内存会更好。

我把它作为一个注释,但是因为它可能不清楚,我将在代码中发布我的答案

如我在评论中所述,分配指针数组没有意义——在64位机器上,这将是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。这有许多优点:

我们只执行一次分配和解除分配,而不是执行更多的分配和解除分配

这通过执行较少的操作来提高速度

我们还改进了内存局部性

我们使用更少的内存

每个内存分配都有一个价格-我们需要一个指针来保存我们分配的内存的内存地址。一个指针在64位机器上花费8字节,在32位机器上花费4字节

通过使用单一分配,我们支付的费用更少

即使我们忽略附加到已分配内存块的元数据(该内存块需要来自内存分配器的内存),这也是正确的

我应该注意到,C实际上并不关心内存块的内容,它都是0和1。这些0和1的含义留给开发人员

甚至printf函数也不关心它正在读取的内存内容——它只是读取内存 根据开发人员指示它遵循的格式,%s通知函数内存与NUL终止的字符串有关

内存对齐是CPU和系统特有的,但这些不适用于单字节字符串。它们适用于多字节类型,如short、int和long。所以在这个例子中,我们不需要担心它们

从这个意义上讲,它基本上意味着开发人员可以自由地管理内存和内容,因为他们认为合适,而不必考虑内存对齐


这并不是说如果您需要使用realloc,分配一块内存总是更好,您可能更喜欢较小的内存块。。。但是通常单个内存块更好。

对于初学者来说,不清楚为什么要为字符串文本hello动态分配内存

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 );
} 
然后在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 );

对于初学者来说,不清楚为什么要为字符串文本hello动态分配内存

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 );
} 
然后在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 );


什么是char**array=mallocpowstrlens,2;?!对于要为每个字符串分配strlen而不是strlen+1的复制字符串,缺少NUL终止符。。。此外,您可能应该将其视为char*,因为您正在执行单个分配-buf+strlens+1将标记第二个字符串的开头。@如果您错了。他只需要分配char*@picklerick类型的strlen指针,再看一次它的语句char**array=mallocpowstrlens,2;并查看数组的类型。@crick是您不理解该声明。声明没有任何意义。什么是char**array=mallocpowstrlens,2;?!对于要为每个字符串分配strlen而不是strlen+1的复制字符串,缺少NUL终止符。。。此外,您可能应该将其视为char*,因为您正在执行单个分配-buf+strlens+1将标记第二个字符串的开头。@如果您错了。他只需要分配char*@picklerick类型的strlen指针,再看一次它的语句char**array=mallocpowstrlens,2;并查看数组的类型。@crick是您不理解该声明。声明没有任何意义。我对答案投了更高的票,即使。。。但是:我建议将strlen的结果存储在变量中。的确,编译器通常会执行优化,但是,如果可能的话,避免重复函数调用是一个很好的实践。感谢您的回答,它工作得非常好!对于如何分配双指针,我有点困惑。至于使用size\u t,是否总是使用size\u t优于int?@AaronGarton函数strlen的返回类型是size\u t。int类型的对象可能太小,无法容纳size\u t类型对象的任何值。即使。。。但是:我建议将strlen的结果存储在变量中。的确,编译器通常会执行优化,但是,如果可能的话,避免重复函数调用是一个很好的实践。感谢您的回答,它工作得非常好!对于如何分配双指针,我有点困惑。至于使用size\u t,是否总是使用size\u t优于int?@AaronGarton函数strlen的返回类型是size\u t。int类型的对象可能太小,无法容纳size\t类型的对象的任何值。@AaronGarton-不客气。请考虑接受这个答案。这是表达感谢的常用方式,所以:接受你的答案,因为它解决了我的问题:D但我不认为一个长字符串是最好的方式来做这件事?@AaronGarton-我编辑了答案来回答你评论中的问题。我希望您会看到,在这种特定情况下,使用一个长字符串实际上更好。如果字符串必须在运行时重新分配,那可能是另一回事。@AaronGarton-不客气。请考虑接受这个答案。这是表达感谢的常用方式,所以:接受你的答案,因为它解决了我的问题:D但我不认为一个长字符串是最好的方式来做这件事?@AaronGarton-我编辑了答案来回答你评论中的问题。我希望您会看到,在这种特定情况下,使用一个长字符串实际上更好。如果必须在运行时重新分配字符串,则可能是另一回事。