【问题标题】:Segmentation fault when passing 2D array to function将二维数组传递给函数时出现分段错误
【发布时间】:2017-05-18 14:37:44
【问题描述】:

我正在尝试将从stdin 读取的字符串中的值直接写入数组,但出现分段错误。既然数组是在我读取N和M之后声明的,内存应该已经分配了吧?

  int main()
{
    long long N;
    long long M;
    scanf("%lld%lld",&N,&M);
    char line[M];
    long long map[N][M];
    for (long long i=0; i<M; i++)
    {
        scanf("%s", &line);
        buildMap(&map, i, &line);
    }

    for (long long i=0; i<N; i++)
        for (long long j=0; j<M; j++)
            printf(&map);

}



void buildMap(long long **map, long long i, char * line)
{
    for (long long j=0; j<strlen(line); j++)
    {
        map[i][j] = line[j]-'0';
    }

【问题讨论】:

  • 任何特殊原因i 在循环中被声明为long longbuildMap 接受int?
  • 数组数组与指向指针的指针不同。请参阅例如 this old answer of mine 以“图形化”解释原因。
  • long long **map 不是二维数组,不能用来指向一个。它不包含有关数组维度的任何信息,因此无法进行二维索引。
  • 那么不能直接通过函数修改数组吗? @FedericoklezCulloca 没有理由,我更正了。
  • 当然可以。但不是这样。只需将指针与尺寸一起传递给它。然后使用它们来计算正确的索引。 (或将指针投射到具有这些尺寸的 VLA)

标签: c arrays pointers segmentation-fault


【解决方案1】:

我已阅读您的代码,并且我假设您正在尝试通过用户输入构建 2D 地图,这是一个字符串(在您的代码中名为“Line”),应该只包含从 0 到 9 的数字。从 0 开始的数字到 9 可能代表地图的不同元素。我猜对了吗?

我复制并修改了你的代码,最后我得到了这样的结果:

program screenshot

如果我猜对了,我先解释一下你的代码编译不成功的原因。

long long M; char line[M];

在这里,您使用了一个变量来声明数组的大小。此语法适用于其他一些编程语言,但不适用于 C。在 C 中,编译源代码时,编译器必须确切知道要为每个函数分配多少堆栈内存空间(在您的情况下为 main() 函数)。由于编译器在尝试编译代码时不知道数组有多大,因此编译失败。

一种常见的解决方案是,我们选择将数组存储在堆中,而不是将数组存储在堆栈中,因为堆内存是在程序运行时动态分配和释放的。换句话说,您可以在获得用户输入后决定分配多少内存。函数 malloc() 和 free() 用于这种操作。

另一个问题是使用“long long **map”。虽然它不会导致编译失败,但它也不会给你预期的结果。当数组的M(数组宽度)是一个已知的常数值时,我们总是倾向于使用“long long map[][M]”作为参数。但是,在您的情况下,由于 M 未知,常见的解决方案是手动计算目标位置,因为无论数组维度如何,数组中的元素始终以线性顺序存储在内存中。

上述两个问题我已经修复,下面贴上修改后的源码,已经编译成功:

#include <malloc.h>
#include <string.h>

void buildMap(int *map, int i, char * line);

int main()
{
  int N;
  int M;
  scanf("%d%d", &N, &M);

  /*Since M (available memory space for "Line") is set by user, we need to build
  "szSafeFormat" to restrict the user's input when typing the "Line". Assuming M
  is set to 8, then "szSafeFormat" will look like "%7s". With the help of 
  "szSafeFormat", the scanf function will be scanf("%7s", Line), ignoring 
  characters after offset 7.*/
  char szSafeFormat[256] = { 0 };
  sprintf(szSafeFormat, "%%%ds", M - 1);

  //char line[M];
  char *Line          = (char *)malloc(sizeof(char) * M);   //raw user input
  char *pszValidInput = (char *)malloc(sizeof(char) * M);   //pure numbers

  //long long map[N][M];
  int *pnMap = (int *)malloc(sizeof(int) * M * N);
  memset(pnMap, 0xFF, M * N * sizeof(int));   //initialize the Map with 0xFF

  for (int i = 0; i < /*M*/N; i++)
  {
    scanf(szSafeFormat, Line);              //get raw user input
    sscanf(Line, "%[0-9]", pszValidInput);  //only accept the numbers
    while (getchar() != '\n');              //empty the stdin buffer

    buildMap((int *)(pnMap + i * M), i, pszValidInput);
  }

  printf("\r\n\r\n");

  for (int i = 0; i < N; i++)
  {
    for (int j = 0; j < M; j++)
    {
      //if the memory content is not 0xFF (means it's a valid value), then print
      if (*(pnMap + i * M + j) != 0xFFFFFFFF)   
      {
        printf("%d", *(pnMap + i * M + j));
      }
    }
    printf("\r\n");
  }

  free(Line);
  free(pszValidInput);
  free(pnMap);

  return 0;
}

void buildMap(int *map, int i, char * line)
{
  for (int j = 0; j < strlen(line); j++)
  {
    (int) *((int *)map + j) = line[j] - '0';
  }
}

我使用了类型“int”而不是“long long”,但是如果你坚持继续使用“long long”应该不会有任何问题。如果继续使用“long long”,则打印出数组值时的条件应更改为:

if (*(pnMap + i * M + j) != 0xFFFFFFFF)

if (*(pnMap + i * M + j) != 0xFFFFFFFFFFFFFFFF)  

还有一些关于用户输入验证的其他修改,我在代码中编写了一些额外的 cmets。

【讨论】:

  • 是的,你猜对了。我最终使用了一个指向线性数组的指针并像`map(i*M+j)`一样遍历地图。
  • @AlexandruAntochi *(map + i * M + j) 是一种简单、高效但仍然非常易读的解决方案。
【解决方案2】:

请记住,C 支持可变长度数组(您已经使用过的东西)。这意味着您实际上可以将维度作为参数传递给函数,并在数组参数的声明中使用它们。也许像

void buildMap(const size_t N, const size_t M, long long map[N][M], long long i, char * line) { ... }

打个电话

buildMap(N, M, map, i, line);

请注意,我已将NM 的类型更改为size_t,这是用于可变长度数组维度的正确类型。您应该相应地更新变量声明,并将"%zu 用于scanf 格式字符串。


请注意,在对buildMap 的调用中,我不使用数组的地址运算符。这是因为数组自然会衰减为指向其第一个元素的指针。通过例如&amp;line 在语义上是不正确的,因为它会将 char (*)[M] 类型的东西传递给函数,而不是 char *

【讨论】:

  • 为什么使用long long 作为维度?那不是要使用的类型。
  • 嗯。它实际上会将map 视为二维数组吗?还是会衰减为指针?
  • @Olaf 因为main 函数中的NM 属于那种类型。
  • @EugeneSh。它应该衰减到long long (*map)[M]
  • @Someprogrammerdude:你可以给出正确的版本,而不是强化这个缺陷。
猜你喜欢
  • 2014-12-10
  • 1970-01-01
  • 2019-04-20
  • 2023-03-08
  • 2017-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-04
相关资源
最近更新 更多