【问题标题】:Char pointer subtraction in CC中的字符指针减法
【发布时间】:2017-01-28 11:38:18
【问题描述】:

我在这个线程中阅读了 C 中的整数指针减法:Pointer subtraction confusion,它很简单,可以掌握和测试。

但是,我尝试使用 char* 复制类似的场景,但我得到的结果没有多大意义。

这是我尝试过的场景:

#include <stdio.h>
#include <string.h>

int main() {

    char a_arr[16] = "";
    char *a = a_arr;
    char b_arr[1] = "";
    char *b = b_arr;

    printf("\nThe amount by which they differ is: %d\n", a-b);
    // a-b = 1, which makes sense since they are 1 char away

    return 0;
}

接下来我尝试的是我无法理解的内容

#include <stdio.h>
#include <string.h>

int main() {

    char a_arr[16] = "";
    char *a = a_arr;
    char b_arr[2] = "";
    char *b = b_arr;

    printf("\nThe amount by which they differ is: %d\n", a-b);
    // a-b = 16, which doesn't really make sense to me..    

    return 0;
}

我的猜测是编译器端有一些填充内容,我认为不应该是这种情况,因为它是一个 char 数组并且不需要对齐..

我不确定为什么它是 16 字节。非常感谢任何帮助!

我使用下面的在线界面编译并运行了这段代码: http://www.tutorialspoint.com/compile_c_online.php

【问题讨论】:

  • 您调用了未定义的行为。两个指针必须指向同一个数组! (或正好过去)您期望减去两个完全不相关的指针会得到什么?什么是“1升减2秒”?
  • @Olaf 是不是和“1 公斤减 2 英里”的结果一样?
  • @SouravGhosh 更像是“1000 安培 - 10 伏”
  • @Olaf 或者“5 页负 178 度”
  • @SouravGhosh:如果 OP 只是摇头或开始思考并对我的第一条评论进行一些研究(关于类比的内容较少,但第一部分),我会感到很受伤。

标签: c pointers pointer-arithmetic


【解决方案1】:

在此示例中,a_arrab_arrb 可能都分配在堆栈上。编译器不必为您提供有关堆栈上变量排列的任何特定保证。所以编译器可能会填充到 16 字节的倍数,或者可能会在 ab 之间引入其他数据,或者可能会在 ab 之间保存寄存器值,或者...。

这就是为什么,正如评论者所指出的,规范不保证减去属于两个不同数组的指针的结果。好消息是您通常不需要这样做,除非您正在编写操作系统或标准库:)。

编辑此外,内存的排列方式,以及保存在寄存器中与堆栈中的内容,可能会根据您的优化级别而改变。我认为这可能不是这里的一个因素,但要记住这一点。

【讨论】:

  • “规范”是 C 标准,显然不允许这样的减法(使它们成为 UB 是 C 方式来表示“你将离开标准”)。 IOW,鼻部守护进程可能会出现,你的房子会被烧毁,或者你的电脑会对你大喊大叫。
【解决方案2】:

您的编译器似乎首先存储b,然后在您的第一个示例中将a 存储在内存中,然后在第二个示例中首先存储a。当我运行它们时,我得到:

The amount by which they differ is: 1

The amount by which they differ is: 2

所以我的编译器总是将b 存储在比a 更低的地址。

你的记忆大概是什么样的:

First Example:
____________________
|B|        A       |
--------------------

Second Example:
______________________
|        A        |B |
----------------------

正如评论者所指出的,无法保证数组的位置。在两个不同的数组中减去指针是未定义的行为。

【讨论】:

    【解决方案3】:

    我把你的程序改写成只会转储内存的东西。这应该让您更好地了解内存中的布局。

    正如其他人所指出的,编译器不为您提供有关内存布局的保证。甚至检查内存地址也可以改变编译器组织其内存的方式。您的问题与其说是关于 C,不如说是关于您的特定编译器的怪癖。

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        char a_arr[16] = "";
        char *a = a_arr;
        char b_arr[1] = "";
        char *b = b_arr;
    
        void *min, *max, *curr;
    
    
        min = &a_arr;
        if (min > (void *)&a) {
            min = &a;
        }
        if (min > (void *)&b_arr) {
            min = &b_arr;
        }
        if (min > (void *)&b) {
            min = &b;
        }
    
        max = (void *)&a_arr + sizeof(a_arr);
        if (max < (void *)&a + sizeof(a)) {
            max = (void *)&a + sizeof(a);
        }
        if (max < (void *)&b_arr + sizeof(b_arr)) {
            max = (void *)&b_arr + sizeof(b_arr);
        }
        if (max < (void *)&b + sizeof(b)) {
            max = (void *)&b + sizeof(b);
        }
    
        // Now print them.
        for (curr = min; curr <= max; ++curr) {
            if (curr == &a_arr)
                printf ("%10p: %10x - a_arr\n", curr, *((char *)curr));
            else if (curr == &a)
                printf ("%10p: %10x - a\n", curr, *((char *)curr));
            else if (curr == &b_arr)
                printf ("%10p: %10x - b_arr\n", curr, *((char *)curr));
            else if (curr == &b)
                printf ("%10p: %10x - b\n", curr, *((char *)curr));
            else
                printf ("%10p: %10x\n", curr, *((char *)curr));
        }
    
        printf ("\nThe amount by which they differ is: %d\n", a-b);
    
        return 0;
    }
    

    这是它在我的机器上的运行方式。注意 b_arr 之后浪费的三个字节。这些字节用于使每个变量从 4 的倍数的地址开始(这称为字边界对齐,非常标准)。

    我怀疑您的编译器在 16 字节边界上对齐 b_arr。这很不寻常,但并不奇怪。编译器为了速度做了最奇怪的事情。

    Here 的另一个问题很好地说明了内存对齐的不可预测性。一般来说,您不应该将内存布局视为确定性。

      ffbfefbc:   ffffffff - b
      ffbfefbd:   ffffffbf
      ffbfefbe:   ffffffef
      ffbfefbf:   ffffffc0
      ffbfefc0:          0 - b_arr
      ffbfefc1:          0
      ffbfefc2:          0
      ffbfefc3:          0
      ffbfefc4:   ffffffff - a
      ffbfefc5:   ffffffbf
      ffbfefc6:   ffffffef
      ffbfefc7:   ffffffc8
      ffbfefc8:          0 - a_arr
      ffbfefc9:          0
      ffbfefca:          0
      ffbfefcb:          0
      ffbfefcc:          0
      ffbfefcd:          0
      ffbfefce:          0
      ffbfefcf:          0
      ffbfefd0:          0
      ffbfefd1:          0
      ffbfefd2:          0
      ffbfefd3:          0
      ffbfefd4:          0
      ffbfefd5:          0
      ffbfefd6:          0
      ffbfefd7:          0
      ffbfefd8:          0
    
    The amount by which they differ is: 8
    

    【讨论】:

      【解决方案4】:

      如果你正确地构建你的测试,你会发现char 指针减法的行为与int 指针减法完全相同。 IE。返回的值是两个指针之间的chars 的数量,不是它们之间的内存地址(可能是也可能不是字节)的数量。

      #include <stdio.h>
      #include <string.h>
      
      int main()
      {
          char a_arr[16] = "";
          char *a = a_arr;
      //    char b_arr[1] = "";
          char *b = &a_arr[8];
      
          printf("\nThe amount by which they differ is: %d\n", b-a);
          // b-a = 8, which makes sense since they are 8 chars away.
      
          printf("\nThe amount by which their addresses differ is: %d\n", (int)b-(int)a);
          // Which will depend on the implementation and may be something unexpected!
      
          return 0;
      }
      

      我使用的微控制器有 16 位数据总线和寄存器,默认情况下,将字符串的字符存储在备用(偶数)地址。在这种情况下,第一个输出是8,第二个输出是16。有编译器选项可以将字符串的字符存储在连续的内存位置,但访问速度较慢,因为它涉及移动 16 位数据寄存器以获取奇数地址字节。

      【讨论】:

      • 是的,您提到的情况有效。这就是为什么我用 2 个不同的数组测试了一个类似的案例,但我不知道这是未定义的行为。
      猜你喜欢
      • 2018-04-10
      • 1970-01-01
      • 2021-06-28
      • 2016-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多