【问题标题】:Why is flattening a multidimensional array in C illegal? [duplicate]为什么在 C 中展平多维数组是非法的? [复制]
【发布时间】:2014-04-04 07:23:47
【问题描述】:

我的书(Kenneth Reek 的 C 指针)说以下内容是非法,尽管它可以正常工作。

  int arr[5][5];
  int *p=&arr[2][2];
  p=p+3; // As array is stored in row major form I think this 
         //should make p point to arr[3][0]

书上说将一行留到下一行是非法的。但我不明白为什么。

【问题讨论】:

    标签: c


    【解决方案1】:

    这本书说它是非法的原因是因为指针运算保证只适用于指向同一数组中元素的指针,或者一个超过末尾的元素。

    arr 是一个由 5 个元素组成的数组,其中每个元素是一个由 5 个整数组成的数组。因此,理论上,如果您想在arr[i] 中拥有指向数组元素的指针,您只能进行指针运算以产生&arr[i][0..4]arr[i]+5 范围内的指针保持i 不变

    例如,假设arr 是 5 个整数的一维。然后指针p 只能指向&arr[0..4]arr+5 中的每一个(最后一个)。多维数组也是如此。

    使用int arr[5][5];,您只能进行指针运算,以便始终拥有位于&arr[i][0..4]arr[i]+5 范围内的指针——这就是规则所说的。这可能会令人困惑,因为这些是数组中的数组,但无论如何规则都是相同的。从概念上讲,arr[0]arr[1] 是不同的数组,即使您知道它们在内存中是连续的,在arr[0]arr[1] 的元素之间进行指针运算也是非法的。请记住,从概念上讲,arr[i] 中的每个元素都是一个不同的数组。

    但是,在您的示例中,p+3 将指向arr[2][2] 末尾的一个位置,因此在我看来它仍然有效。这是一个糟糕的示例选择,因为它会使p 精确指向末尾,使其仍然有效。如果作者选择了p+4,那么这个例子就是正确的。

    无论哪种方式,我在使用类似方法在 C 中展平多维数组时从未遇到任何问题。

    也看到这个问题,还有其他有用的信息:One-dimensional access to a multidimensional array: well-defined C?

    【讨论】:

    • C stardand 是否允许arr[5][5] 的子数组之间有间隙,例如arr[1][0] 不直接跟随arr[0][4]
    • @leeduhem 不,我认为它不允许。 C 标准规定数组元素在内存中是连续的,并且它们以行优先顺序存储(最后一个下标变化最快)。请参阅第 6.2.5.1 节,“数组下标”。由此可见,两者之间不能有间隙。
    • 看起来这可以保证像*(p+4)这样的表达式的定义行为,因为如果p = &arr[2][2]p+4总是指向arr[3][1]
    • @leeduhem 不,它不能保证,因为p+4 超出了它指向的数组的范围。是的,你知道在arr[2] 之后还有另一个数组,但即使它是连续的,但不管它值多少钱,它都是 another 数组,所以你不能这样做。这是 C 的一个黑暗角落。理论上,它可能不起作用,但我相信它总是有效的。我想这只是标准的不一致。
    • 同意,这可能只是不一致。
    【解决方案2】:

    我对此凝神了一会儿,我会尽力解释我认为他来自哪里,尽管没有阅读这本书,这充其量只是猜测。 p>

    首先,从技术上讲,您提议(或他提议)的增量并不违法; 取消引用它是。该标准允许您将指针前进到数组序列的最后一个元素的过去,从该元素中获取它以进行评估,但不能用于取消引用。将其更改为 p = p + 4 并且 两者 都是非法的。

    除此之外,数组的线性占用空间无法承受,ar[2] 有一个类型,它是int[5]。如果您不相信,请考虑以下内容,所有这些都输入正确:

    int ar[5][5];
    int (*sub)[5] = ar+2;   // sub points to 3rd row
    int *col = *sub + 2;    // col points to 3rd column of third row.
    int *p = col + 3;       // p points to 5th colum of third row.
    

    这是否落在ar[3][0] 上不相关您超出了参与指针数学的维度的声明大小。结果不能合法地取消引用,如果它大于 3 偏移量,甚至不能合法地评估。

    记住,被寻址的数组是ar[2];不只是ar,而且 said-same 被声明为 size=5。它与其他两个相同的数组支撑起来与正在完成的寻址当前无关。我相信Christoph's answer 作为重复提出的问题应该是选择彻底解决的问题。特别是对 C99 §6.5.6, p8 的引用,虽然冗长,但在下面出现:

    当一个整数类型的表达式被添加或减去时 从一个指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象的一个​​元素,而数组 足够大,结果指向一个元素偏移量 原始元素使得下标的差异 结果和原始数组元素等于整数表达式。 换句话说,如果表达式 P 指向一个 数组对象,表达式 (P)+N(等价于 N+(P))和 (P)-N (其中 N 的值为 n)分别指向第 i+n 个和 数组对象的第 i 个元素,如果它们存在。此外,如果 表达式 P 指向数组对象的最后一个元素, 表达式 (P)+1 指向数组对象的最后一个元素, 如果表达式 Q 指向数组的最后一个元素 对象,表达式 (Q)-1 指向数组的最后一个元素 目的。如果指针操作数和结果都指向元素 相同的数组对象,或数组的最后一个元素 对象,评估不应产生溢出; 否则, 行为未定义。 如果结果指向最后一个元素之后 数组对象的,它不应用作一元的操作数 * 被评估的运算符。

    对垃圾邮件感到抱歉,但我认为粗体突出显示的内容与您的问题相关。通过按原样寻址,您将离开正在寻址的数组,并因此进入 UB。简而言之,它(通常)有效,但不合法。

    【讨论】:

      【解决方案3】:

      是的。它在 C 语言中是非法的。事实上,这样做是在向编译器施加压力。 p 指向元素arr[2][2](并且是指向int 类型的指针),即第三行的第三个元素。语句p=p+3; 会将指针p 递增到arr[2][5],相当于arr[3][0]
      但是,只要在某些架构上将内存分配为 2 ( 2n ) 的幂,这就会失败。现在在这种情况下,内存分配将四舍五入到 2n,即,在您的情况下,每一行将四舍五入到 64 字节。
      请参阅一个测试程序,其中分配的内存是 10 个整数的 5 次分配。在某些机器上,内存分配是 16 字节的倍数,因此每次分配请求的 40 字节向上舍入为 48 字节:

      #include <stdio.h>
      #include <stdlib.h>
      
      extern void print_numbers(int *num_ptr, int n, int m);
      extern void print_numbers2(int **nums, int n, int m);
      
      int main(void)
      {
          int **nums;
          int n = 5;
          int m = 10;
          int count = 0;
      
          // Allocate rows
          nums = (int **)malloc(n * sizeof(int *));
      
          // Allocate columns for each row
          for (int i = 0; i < n; i++)
          {
              nums[i] = (int *)malloc(m * sizeof(int));
              printf("%2d: %p\n", i, (void *)nums[i]);
          }
      
          // Populate table
          for (int i = 0; i < n; i++)
              for (int j = 0; j < m; j++)
                  nums[i][j] = ++count;
      
          // Print table
          puts("print_numbers:");
          print_numbers(&nums[0][0], n, m);
          puts("print_numbers2:");
          print_numbers2(nums, n, m);
          return 0;
      }
      
      void print_numbers(int *nums_ptr, int n, int m)
      {
          int (*nums)[m] = (int (*)[m])nums_ptr;
      
          for (int i = 0; i < n; i++)
          {
              printf("%2d: %p\n", i, (void *)nums[i]);
              for (int j = 0; j < m; j++)
              {
                  printf("%3d", nums[i][j]);
              }
              printf("\n");
          }
      }
      
      
      void print_numbers2(int **nums, int n, int m)
      {
          for (int i = 0; i < n; i++)
          {
              printf("%2d: %p\n", i, (void *)nums[i]);
              for (int j = 0; j < m; j++)
                  printf("%3d", nums[i][j]);
              printf("\n");
          }
      }
      

      Mac OS X 10.8.5 上的示例输出; GCC 4.8.1:

       0: 0x7f83a0403a50
       1: 0x7f83a0403a80
       2: 0x7f83a0403ab0
       3: 0x7f83a0403ae0
       4: 0x7f83a0403b10
      print_numbers:
       0: 0x7f83a0403a50
        1  2  3  4  5  6  7  8  9 10
       1: 0x7f83a0403a78
        0  0 11 12 13 14 15 16 17 18
       2: 0x7f83a0403aa0
       19 20  0  0 21 22 23 24 25 26
       3: 0x7f83a0403ac8
       27 28 29 30  0  0 31 32 33 34
       4: 0x7f83a0403af0
       35 36 37 38 39 40  0  0 41 42
      print_numbers2:
       0: 0x7f83a0403a50
        1  2  3  4  5  6  7  8  9 10
       1: 0x7f83a0403a80
       11 12 13 14 15 16 17 18 19 20
       2: 0x7f83a0403ab0
       21 22 23 24 25 26 27 28 29 30
       3: 0x7f83a0403ae0
       31 32 33 34 35 36 37 38 39 40
       4: 0x7f83a0403b10
       41 42 43 44 45 46 47 48 49 50  
      

      Win7 上的示例输出; GCC 4.8.1:

      【讨论】:

      • p+3 指向与arr[3][0] 相同的元素。对我来说看起来有效。
      • @WhozCraig;是的。我同意。你说的对。事实上,我应该增加列而不是行。
      • @leeduhem;我没有强有力的证据:)。但是一旦我遇到了一些类似的问题。我向我的导师提出了这个问题,他回答了一个测试程序,我将其包含在我的回答中。
      • 我从未投反对票,并将放弃我的评论,因为它指出的唯一问题已得到解决。
      • @leeduhem 确实如此。 C99 的第 6.5.2.1 节第 3 段:“[...] 由此得出,数组以行优先顺序存储(最后一个下标变化最快)”以及 6.2.5 条目 20:“[...]数组类型描述了一组连续分配的非空对象。”
      猜你喜欢
      • 2019-07-30
      • 2011-07-07
      • 1970-01-01
      • 2010-10-06
      • 1970-01-01
      • 2017-09-11
      相关资源
      最近更新 更多