在C语言中,字符指针的减法运算

4

我在这个线程里读到了有关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;
}

我的猜测是编译器的一些填充操作导致了这种情况,但我认为应该不需要进行对齐操作,因为它是一个字符数组。我不确定为什么它是16个字节。非常感谢您的帮助!我使用了以下在线界面来编译和运行这段代码:http://www.tutorialspoint.com/compile_c_online.php

3
你触发了未定义行为。这两个指针必须指向同一数组(或恰好超出该数组)。如果你减去两个完全不相关的指针,你期望得到什么?"1升减去2秒"代表什么意思? - too honest for this site
@Olaf 这不是和 "1千克 减 2 英里" 得出的结果一样吗? - Sourav Ghosh
@SouravGhosh 更像是“1000安培减去10伏特”。 - too honest for this site
@Olaf 或许是 "5页减去178度" - Sourav Ghosh
1
@SouravGhosh:我想知道OP是只摇了摇头还是开始思考并对我的第一条评论进行了一些研究(与类比有关系较小,但与第一部分有关)。 - too honest for this site
4个回答

4
在这个示例中,a_arrab_arrb可能全部分配在堆栈上。编译器不需要向您提供有关堆栈上变量排列的任何特定保证。因此,编译器可能会将填充为16字节的倍数,或者可能在ab之间引入其他数据,或者可能在ab之间保存寄存器值。这就是评论者指出规范不保证属于两个不同数组的指针相减结果的原因。好消息是,通常情况下,除非您正在编写操作系统或标准库,否则您通常不需要这样做 :)。 编辑 此外,内存排列方式以及在寄存器还是在堆栈上保存什么可能会因您的优化级别而改变。我认为这里可能没有问题,但要记住这一点。

“specs” 是 C 标准,它明确禁止这样的减法操作(让它们成为未定义行为是 C 的说法,意思是“你正在脱离标准”)。换句话说,可能会出现奇怪的错误,比如程序崩溃、文件丢失,甚至是计算机对你大喊大叫。 - too honest for this site

1

在你的第一个示例中,编译器似乎先将b存储在内存中,然后是a;在第二个示例中则是先存储a。当我运行它们时,得到的结果如下:

The amount by which they differ is: 1

and

The amount by which they differ is: 2

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

你的内存可能看起来像这样:

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

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

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

1
我把你的程序改写成了一个只会转储内存的东西。这应该能更好地让你了解内存中的布局。
正如其他人指出的那样,编译器不能保证内存布局。即使检查内存地址,也可能会改变编译器组织内存的方式。你的问题不是关于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的倍数地址开始(这称为字边界对齐,非常标准)。
我怀疑你的编译器正在将b_arr对齐到16字节边界。这很不寻常但也不奇怪。编译器为了速度而做出最奇怪的事情。
这里还有一个问题(这里),很好地说明了内存对齐的不可预测性。总的来说,您不应该将内存布局视为确定性。
  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

0
如果您正确构建测试,您会发现char指针减法的行为与int指针减法完全相同。也就是说,返回的值是两个指针之间的char数目,而不是它们之间的内存地址数量(这可能是字节或不是字节)。
#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位数据寄存器以获取奇地址字节。

是的,你提到的情况是可以工作的。这就是为什么我测试了一个类似的情况,使用了两个不同的数组,但我不知道它是未定义行为。 - rhino--

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接