在 C 中使用指针算术后,这个奇怪的输出是什么?

What is this weird output after using pointer Arithemtic in C?

我在代码中的目标是将某种输入解析为关于所有 space 的词,但同时使用那些 space 来表示词的变化。这里的逻辑是,任何时候遇到 space 它都会循环,直到不再有 space 字符,然后当它遇到一个单词时,它会循环直到遇到 space 字符或 ' \0' 同时将每个字符放入二维数组中数组内部数组的一个索引中。然后在 while 循环再次继续之前它索引到下一个数组。

我几乎可以肯定逻辑的实现足够好,可以正常工作,但是我得到了下面列出的这个奇怪的输出我以前遇到过同样的问题,当弄乱指针和诸如此类的东西时,但我就是不能得到这个无论我做什么,都要工作。关于为什么我真的很好奇背后的原因有什么想法吗?

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


void print_mat(char **arry, int y, int x){
  for(int i=0;i<y;i++){
    for(int j=0;j<x;j++){  
      printf("%c",arry[i][j]);
    }
    printf("\n");
    }
}

char **parse(char *str)
{
char **parsed=(char**)malloc(sizeof(10*sizeof(char*)));
        for(int i=0;i<10;i++){
                parsed[i]=(char*)malloc(200*sizeof(char));
                }

        char **pointer = parsed;
        while(*str!='[=10=]'){
                if(*str==32)
                {
                        while(*str==32 && *str!='[=10=]'){
                                str++;
                        }
                }
                  while(*str!=32 && *str!='[=10=]'){
                    (*pointer) = (str);
                    (*pointer)++;
                    str++;
                  }
        pointer++;
        }
        return parsed;
}

int main(){
  char str[] = "command -par1 -par2 thething";
  char**point=parse(str);
  print_mat(point,10,200);
  return 0;
}

 -par1 -par2 thethingUP%�W���U�6o� X%��U�v;,���UP%���cNjW��]A�aW�Ӹto�8so�z�

 -par2 thethingUP%�W���U�6o� X%��U�v;,���UP%���cNjW��]A�aW�Ӹto�8so�z�

 thethingUP%�W���U�6o� X%��U�v;,���UP%���cNjW��]A�aW�Ӹto�8so�z�

UP%�W���U�6o� X%��U�v;,���UP%���cNjW��]A�aW�Ӹto�8so�z�


我也尝试简单地索引二维数组但无济于事

char **parse(char *str)
{
        int i, j;
        i=0;
        j=0;
char **parsed=(char**)malloc(sizeof(10*sizeof(char*)));
        for(int i=0;i<10;i++){
                parsed[i]=(char*)malloc(200*sizeof(char));
                }

        while(*str!='[=12=]'){
          i=0;
                if(*str==32)
                {
                        while(*str==32 && *str!='[=12=]'){
                                str++;
                        }
                }
                  while(*str!=32 && *str!='[=12=]'){
                    parsed[j][i] = (*str);
                    i++;
                    str++;
                  }
        j++;
        }
        return parsed;
}

输出:

command�&�v�U`'�v�U0(�v�U)�v�U�)�v�U
-par1
-par2
thething
makefile:5: recipe for target 'build' failed
make: *** [build] Segmentation fault (core dumped)

您的代码中有几个问题:

  • 您的程序正在泄漏内存。
  • 您的程序正在访问不属于它的内存,这是 UB。

让我们一一讨论 -

第一个问题 - 内存泄漏:

检查 parse() 函数的这一部分:

                  while(*str!=32 && *str!='[=10=]'){
                    (*pointer) = (str);

在外部 while 循环的第一次迭代中,*pointer 将为您提供 parsed 数组的第一个成员,即 parsed[0],它是指向 char。请注意,在外部 while 循环之前,您正在动态地将内存分配给 parsed[0]parsed[1]... parsed[9] 中的 parse() 指针。在内部 while 循环中,您将它们指向 str。因此,他们将丢失动态分配的内存引用并导致内存泄漏。

第二个问题 - 访问不属于它的内存:

如上所述,指针 parsed[0]parsed[1] 等将指向 str 的内部 while 循环中的任何当前值 parse() 函数。这意味着,指针 parsed[0]parsed[1] 等将指向数组 str(在 main() 中定义)的某个元素。在 print_mat() 函数中,您正在传递 200 并访问数组 arry0199 索引的每个指针。因为,arry 指针指向大小为 29str 数组,这意味着,您的程序正在访问超出其 UB 大小的内存(数组)。

让我们在不做太多更改的情况下解决代码中的这些问题:

内存泄漏:

不是将指针指向 str,而是将 str 的字符分配给分配的内存,如下所示:

                  int i = 0;
                  while(*str!=32 && *str!='[=11=]'){
                     (*pointer)[i++] = (*str);
                     str++;
                  }

访问不属于它的内存:

你应该记住的一点:
在 C 中,字符串实际上是 one-dimensional 以空字符 [=50=].

结尾的字符数组

首先,动态分配内存后清空字符串,以便在打印时识别未使用的指针:

        for(int i=0;i<10;i++){
            parsed[i]=(char*)malloc(200*sizeof(char));
            parsed[i][0] = '[=12=]';
        }

在将单词写入 parsed 数组指针后,用空终止符终止所有字符串:

                  int i = 0;
                  while(*str!=32 && *str!='[=13=]'){
                     (*pointer)[i++] = (*str);
                     str++;
                  }
                  // Add null terminator
                  (*pointer)[i] = '[=13=]'; 

print_mat() 中,确保一旦您点击了空终止符,就不要超出它。修改内for循环条件:

    for(int j = 0; (j < x) && (arry[i][j] != '[=14=]'); j++){
       printf("%c",arry[i][j]);

你不需要逐个字符地打印字符串,你可以简单地使用 %s 格式说明符来打印一个字符串,就像这样 -

    for (int i = 0;i < y; i++) {
        if (arry[i][0] != '[=15=]') {
            printf ("%s\n", arry[i]);
        }
    }

根据上述建议的更改(这是您的程序正常运行所需的最小更改),您的代码将如下所示:

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

void print_mat (char **arry, int y) {
    for (int i = 0; i < y; i++) {
        if (arry[i][0] != '[=16=]') {
            printf ("%s\n", arry[i]);
        }
    }
}

char **parse(char *str) {
    char **parsed = (char**)malloc(sizeof(10*sizeof(char*)));
    // check malloc return

    for(int i = 0; i < 10; i++){
        parsed[i] = (char*)malloc(200*sizeof(char));
        // check malloc return
        parsed[i][0] = '[=16=]';
    }

    char **pointer = parsed;
    while (*str != '[=16=]') {
        if(*str == 32) {
            while(*str==32 && *str!='[=16=]') {
                str++;
            }
        }

        int i = 0;
        while (*str != 32 && *str != '[=16=]') {
            (*pointer)[i++] = (*str);
            str++;
        }

        (*pointer)[i] = '[=16=]';
        pointer++;
    }
    return parsed;
}

int main (void) {
    char str[] = "command -par1 -par2 thething";

    char **point = parse(str);
    print_mat (point, 10);

    // free the dynamically allocate memory

    return 0;
}

输出:

command
-par1
-par2
thething

您的代码实现可以做很多改进,例如-

  • 如上所示,您可以使用 %s 格式说明符而不是逐字符打印字符串等。我将由您来识别这些更改并修改您的程序。
  • 仅在 str.
  • 中有单词的地方将内存分配给 parsed 数组指针
  • 不是将固定大小的内存(即200)分配给parsed数组指针,而是仅分配字大小的内存。

几点建议:

  • 始终检查 return 函数的值,例如 malloc
  • 确保在程序完成后释放动态分配的内存。

您可以通过更简单的方式实现您想要的。

首先,定义一个函数来检查字符(分隔符)列表中是否存在字符(分隔符):

// Returns true if c is found in a list of separators, false otherwise.
bool belongs(const char c, const char *list)
{
    for (const char *p = list; *p; ++p)
        if (*p == c) return true;
    
    return false;
}

然后,定义一个函数,将给定的字符串拆分为由一个或多个分隔符分隔的标记:

// Splits a string into into tokens, separated by one of the separators in sep
bool split(const char *s, const char *sep, char **tokens, size_t *ntokens, const size_t maxtokens)
{
    // Start with zero tokens.
    *ntokens = 0;
    
    const char *start = s, *end = s;
    for (const char *p = s; /*no condtition*/; ++p) {
        
        // Can no longer hold more tokens? Exit.
        if (*ntokens == maxtokens)
            return false;
        
        // Not a token? Continue looping.
        if (*p && !belongs(*p, sep))
            continue;
        
        // Found a token: calculate its length.
        size_t tlength = p - start;
        
        // Empty token?
        if (tlength == 0) {
            // And reached the end of string? Break.
            if (!*p) break;
            
            // Not the end of string? Skip it.
            ++start;
            continue;
        }
        
        // Attempt to allocate memory.
        char *token = malloc(sizeof(*token) * (tlength + 1));
        
        // Failed? Exit.
        if (!token)
            return false;
        
        // Copy the token.
        strncpy(token, start, tlength+1);
        token[tlength] = '[=11=]';
        
        // Put it in tokens array.
        tokens[*ntokens] = token;
        
        // Update the number of tokens.
        *ntokens += 1;
        
        // Reached the end of string? Break.
        if (!*p) break;
        
        // There is more to parse. Set the start to the next char.
        start = p + 1;
    }
    
    return true;
}

这样称呼它:

int main(void)
{
    char command[] = "command -par1 -par2 thing";
    
    const size_t maxtokens = 10;
    
    char **tokens = malloc(sizeof *tokens * maxtokens);
    if (!tokens) return 1;
    
    size_t ntokens = 0;
    
    split(command, " ", tokens, &ntokens, maxtokens);
    
    // Print all tokens.
    printf("Number of tokens = %ld\n", ntokens);
    for (size_t i = 0; i < ntokens; ++i)
        printf("%s\n", tokens[i]);
    
    // Release memory when done.
    for (size_t i = 0; i < ntokens; ++i)
        free(tokens[i]);
    
    free(tokens);
}

输出:

Number of tokens = 4
command
-par1
-par2
thing