在 C 中插入 2d 数组时,标记字符串被切断

Token string cuts off when inserting into 2d array in C

提问人:Januar Soepangat 提问时间:5/1/2023 更新时间:5/2/2023 访问量:72

问:

我正确地标记了一行字符串中的单个单词;但是,将它们插入 2D 数组会切断部分令牌。我也有 NULL 问题,代码导致段错误。

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

#define MAX_FILE_LENGTH 30
#define MAX_COURSE_LENGTH 30
#define MAX_LINE_LENGTH 1000

void trim(char* str) {
  int l = strlen(str);
  if (str[l - 1] == '\n') {
    str[l - 1] = 0;
  }
}

int main() {
  char filename[MAX_FILE_LENGTH]; 
  char arr[MAX_COURSE_LENGTH][MAX_COURSE_LENGTH];
  const char delim[] = " ";
  char* token;
  int course = 0;
  char c;
  FILE* fp;
  int N = 0;    // number of lines in file

  printf("This program will read, from a file, a list of courses and their prerequisites and will print the list in which to take courses.\n");
  printf("Enter filename: ");
  scanf("%s%c", filename, &c);

  fp = fopen(filename, "r");

  if (fp == NULL) {
    printf("Could not open file %s. Exit\n", filename);
    printf("\nFailed to read from file. Program will terminate.\n");
    return -1;
  }

  while (!feof(fp) && !ferror(fp)) {
    int i = 0;
    if (fgets(arr[N], MAX_LINE_LENGTH, fp)) {
      trim(arr[N]);
      printf("Full line: |%s|\n", arr[N]);
      token = strtok(arr[N], delim);
      arr[N][i] = *token;
      printf("N = %d, i = %d, token = %s arr[%d][%d]: %s\n", N, i, token, N, i, &arr[N][i]);
      while (token != NULL) {
        i++;
        token = strtok(NULL, " \n");
        printf("token at arr[%d][%i]: %s value at arr[%d][%d]: %s\n", N, i, token, N, i, &arr[N][i]);
        arr[N][i] = *token;
        printf("N = %d, i = %d, token = %s arr[%d][%d]: %s\n", N, i, token, N, i, &arr[N][i]);
      }
      N++;
    }
  }
  fclose(fp);
  return 0;
}

我得到的输出如下:

Full line: |c100 c200|
N = 0, i = 0, token = c100 arr[0][0]: c100
token at arr[0][1]: c200 value at arr[0][1]: 100
N = 0, i = 1, token = c200 arr[0][1]: c00
token at arr[0][2]: (null) value at arr[0][2]: 00
zsh: segmentation fault  ./a.out

我的文件是课程列表,我将使用先修课程列表构建邻接矩阵。

c100 c200
c300 c200 c100
c200 c100

在插入令牌之前,我尝试将每个索引重置为 NULL 或“\0”,但发生了相同的结果。在内部数组的 [N][0] 第 [N] 个索引中插入第一个单词是有效的,但是在插入到内部数组的其他索引中时,我缺少一些东西。

C 字符串 多维数组 strtok 邻接矩阵

评论

0赞 tadman 5/1/2023
PSA:尝试对文件名等内容使用更长的缓冲区,尤其是当它们包含路径时。30个字符非常吝啬。
2赞 Some programmer dude 5/1/2023
OT:你的功能有一些缺陷......1)你不检查或处理指针;2)如果字符串为空,则使用索引;3) 术语“修剪”通常意味着从字符串的开头和结尾删除所有空格。请阅读从 fgets() 输入中删除尾随换行符,以了解在调用后删除换行符的更好方法。trimNULL-1fgets
1赞 Some programmer dude 5/1/2023
另外 OT:请阅读为什么“while( !feof(file) )”总是错的?
2赞 Allan Wind 5/1/2023
segfault 是由于 token 为 NULL 时造成的。*token
0赞 Ruud Helderman 5/1/2023
arr[N][i] = *token;完全没有意义。请注意,不是第二个单词的开头,而是第一个单词的第二个字母。arr[N][1]

答:

3赞 Some programmer dude 5/1/2023 #1

比我上面的所有评论更糟糕的是,这可能会导致未定义的行为,是您用于打印单个字符的活动且始终 UB。%s

任务

arr[N][i] = *token;

将单个字符 分配给单个字符元素。token[0]arr[N][i]

然后,你使用 来打印 ,但该格式需要一个你没有的以 null 结尾的字符串%s&arr[N][i]%s

要有一个字符串,你需要一个字符数组,你可能应该做的是这样的(但我只是猜测):

char arr[MAX_COURSE_LENGTH][MAX_COURSE_LENGTH][MAX_COURSE_LENGTH];

strcpy(arr[N][i], token);

正如 Allan Wind 所提到的,你忘记了可以(并且将)返回指针。strotkNULL

另请注意,所有大写名称通常用于宏和符号常量。这使得看到名称被用作索引变量有点令人困惑。N

2赞 William Pursell 5/1/2023 #2

考虑以下几行:

  while (token != NULL) {
    token = strtok(NULL, " \n");
    arr[N][i] = *token;

(为了清楚起见,我省略了几行。这从根本上是有缺陷的,因为可以返回 NULL(事实上,您期望它在某些时候返回),但是一旦为 NULL,尝试读取就是未定义的行为。编写此代码的标准惯用方法是:strtoktoken*token

while( (token = strtok(NULL, " \n")) != NULL ){ ... }

惯用代码有效。使用它。同样,在上一个循环中,不要只写 ,只需编写 对每次迭代进行 feof/ferror 检查是没有帮助的,并且代码看起来很不寻常。惯用代码乍一看可能看起来很奇怪,但它通常很容易理解,并且是构造循环的正确方法。while( !feof(...) && ! ferror(...) { if( fgets (...) ) ...}while( fgets (....)) {...}

2赞 Allan Wind 5/1/2023 #3

前面的回答已经涵盖了技术问题:

  1. trim()通常是指字符串开头和结尾的空格,但在这种情况下,您只需删除试用换行符即可。如 @Someprogrammerdude 所述,对于空字符串,它不能按预期工作。

  2. strtok()当没有其他要处理的内容时,将返回 NULL。当您随后取消引用时,这就是导致段错误的原因。*token

  3. adj[N][i]在本例中是指 token 的第 i 个字符。这就是为什么您的输入不符合预期的原因

  4. 您提到要构建邻接矩阵,但不是邻接矩阵,而是存储字符串的 2d 字符数组。adj

我在这里为你写了相当多的代码来实现邻接矩阵。我不清楚你是否希望类依赖于它的要求或相反(这只是交换的问题,如果相反:courseprereqprocess()

#include <stdio.h>
#include <stdlib.h>
#define POSIX_C_SOURCE 200809L
#include <string.h> 

#define DELIM " "
#define MAX_FILE_LENGTH 30
#define MAX_COURSE_LENGTH 30
#define MAX_LINE_LENGTH 1000

typedef struct adjacency_matrix {
    size_t nodes_len;
    char **nodes;
    char *edges;
} adjacency_matrix;

struct adjacency_matrix *adjacency_matrix_create(size_t nodes_len) {
    adjacency_matrix *am = malloc(sizeof *am);
    if(!am)
        return NULL;
    am->nodes_len = nodes_len;
    am->nodes = calloc(nodes_len, sizeof *am->nodes);
    am->edges = calloc(nodes_len * nodes_len, 1);
    return am;
}

int adjacency_matrix_node(adjacency_matrix *am, char *node) {
    int i = 0;
    for(; am->nodes[i]; i++)
        if(!strcmp(am->nodes[i], node))
            return i;
    if(i == am->nodes_len)
        return -1;
    am->nodes[i] = strdup(node);
    return i;
}

void adjacency_matrix_edge(adjacency_matrix *am, size_t a, size_t b) {
    am->edges[am->nodes_len * a + b] = 1;
}

void adjacency_matrix_print(const adjacency_matrix *am) {
    for(size_t i = 0; am->nodes[i]; i++) {
        printf("%s:", am->nodes[i]);
        for(size_t j = 0; am->nodes[j]; j++) {
            if(am->edges[i * am->nodes_len + j])
                printf(" %s", am->nodes[j]);
        }
        printf("\n");
    }
}

void adjacency_matrix_free(adjacency_matrix *am) {
    if(!am)
        return;
    free(am->nodes);
    free(am->edges);
    free(am);
}

void trim(char *str) {
    str[strcspn(str, "\n")] = '\0';
}

int process(adjacency_matrix *am, char *line) {
    trim(line);
    int course;
    for(int i = 0;; i++) {
        char *token = strtok(i ? NULL : line, DELIM);
        if(!token)
            break;
        if(!i) {
            course = adjacency_matrix_node(am, token);
            if(course == -1)
                return EXIT_FAILURE;
            continue;
        }
        int prereq = adjacency_matrix_node(am, token);
        if(prereq == -1)
            return EXIT_FAILURE;
        adjacency_matrix_edge(am, course, prereq);
    }
    return EXIT_SUCCESS;
}

int main() {
    printf("This program will read, from a file, a list of courses and their prerequisites and will print the list in which to take courses.\n");
    printf("Enter filename: ");
    char filename[MAX_FILE_LENGTH];
    scanf("%s", filename);

    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("Could not open file %s. Exit\n", filename);
        printf("\nFailed to read from file. Program will terminate.\n");
        return EXIT_FAILURE;
    }
    adjacency_matrix *am = adjacency_matrix_create(MAX_COURSE_LENGTH);
    for(;;) {
        char line[MAX_LINE_LENGTH];
        if (!fgets(line, MAX_LINE_LENGTH, fp))
            break;
        process(am, line);
    }
    fclose(fp);
    adjacency_matrix_print(am);
    adjacency_matrix_free(am);
}

这是相应的输出:

c100: c200
c200: c100
c300: c100 c200