不同类型指针之间的减法

时间:2016-11-16 11:32:08

标签: c pointers subtraction pointer-arithmetic

我试图找到两个变量之间的内存距离。具体来说,我需要找到char []数组和int之间的距离。

    char data[5];
    int a = 0;

    printf("%p\n%p\n", &data[5], &a);

    long int distance = &a - &data[5];

    printf("%ld\n", distance);

当我在没有最后两行的情况下运行我的程序时,我得到了两个变量的正确内存地址,如下所示:

   0x7fff5661aac7
   0x7fff5661aacc

现在我理解,如果我没错,两者之间有5个字节的距离(0x7fff5661aac8,0x7fff5661aac9,0x7fff5661aaca,0x7fff5661aacb,0x7fff5661aacc)。

为什么我不能减去类型(int *)和类型(char *)之一的指针。两者都是指内存地址..我该怎么做才能计算两者之间的距离,以字节为单位?我尝试了两个指针中的一个,但它没有用。

我得到:“错误:'char *'和'int *'不是指向兼容类型的指针”。感谢大家帮助我

7 个答案:

答案 0 :(得分:8)

不,这是不可能的。

首先,您只能减去(到)“兼容”类型的指针,此处intchar不兼容。因此,减法是不可能的。

那就是说,即使两者都是兼容类型的指针,那么,以下内容也会出现。

所以,其次你不能只减去两个任意指针,它们必须基本上是同一个数组的(元素的地址)的一部分。 Othweise,它调用undefined behavior

引用C11,章节§6.5.6,附加运算符

  

当减去两个指针时,两个指针都指向同一个数组对象的元素,   或者超过数组对象的最后一个元素;结果是差异   两个数组元素的下标。 [....]

第三,另一个重点,减去两个指针的结果是ptrdiff_t类型,一个有符号整数类型。

  

[...]结果的大小是实现定义的,   并且其类型(有符号整数类型)在ptrdiff_t标头中定义为<stddef.h>。 [...]

因此,要打印结果,您需要使用%td格式说明符。

答案 1 :(得分:2)

在标准PC上,没有任何东西可以防止你将两个指针都转换为可以保存指针值的整数类型,并减去两个整数。

并不保证这样的整数类型存在于所有体系结构中(但在许多常见系统上都存在) - 想象一下分段内存的信息多于单个数字。如果整数类型不适合,则转换的行为未定义。

从标准草案n1570,6.3.2.3/6:

  

任何指针类型都可以转换为整数类型。除了   之前指定的,结果是实现定义的。如果   结果不能用整数类型表示,行为是   未定义。结果不必在任何值的范围内   整数类型。

通常地址之间的差异将是人们期望的(连续声明的变量在内存中彼此相邻),并且可以用来告诉堆栈增长的方向等。

探索用整数和指针做的其他事情可能会很有趣。

Olaf评论说,如果你“将[算术运算结果]转换回指针,你就会调用UB。”这不一定是这样;它取决于整数值。标准草案在6.3.2.3/5中说明如下:

  

整数可以转换为任何指针类型。除了以前   指定,结果是实现定义,可能不是   正确对齐,可能不指向引用的实体   类型,可能是陷阱表示

(由我强调。)如果我们通过向结构地址添加偏移来计算结构成员的地址,我们显然已经处理了上述问题,因此需要实现。肯定不是UB;如果我们不能使用整数,很多嵌入式系统都会失败 - &gt;指针转换,并通过结果指针访问该内存。我们必须确保系统允许它,并且地址是合理的。

该段有一个脚注:

  

用于将指向整数或整数的指针转换为指针的映射函数   与执行环境的寻址结构保持一致。

也就是说,它们意味着不会让用户惊讶。虽然理论上在存储器中相邻的不相关对象的地址可以被投射到完全不同的整数值,但它们不应该被认为是。例如,用户可以合理地期望将线性存储器投影到线性整数空间中,从而保持排序和距离。

<小时/> 我还应该强调(正如我在一篇评论中所做的那样)标准不是世界。它必须适应并保证各种机器。因此,标准只能是最小的共同标准。如果我们可以缩小我们考虑的架构范围,我们可以做出更好的保证。

一个常见的例子是整数寄存器中可能存在陷阱值,或者表示从未初始化寄存器读取的标志,该寄存器也会陷阱;这些都是标准中广泛的UB案例的原因,而这些案例根本不适用于您的PC。

答案 2 :(得分:2)

指针减法仅针对同一数组中的指针(或刚刚超过数组的最后一个元素)定义。任何其他用途都是未定义的行为。让我们忽略你的实验。

当减去同一类型的两个指针到同一个数组对象的元素时,结果就是数组索引的差异。您可以将有符号整数结果(类型为ptrdiff_t)添加到第一个指针并获取第二个指针的值,或者从第二个指针中减去结果并获取第一个指针的值。实际上,结果是两个指针的字节地址的差异除以指向的对象的大小。这就是为什么允许减去不兼容类型的指针是没有意义的,特别是当引用的对象类型具有不同的大小时。当减去指针指向不同大小的对象时,如何将字节地址的差异除以指向的对象的大小?

尽管如此,出于实验目的,您可以将两个指针(指向不同的对象)转换为char *并减去它们,并且许多编译器只会将字节地址的差异作为数字给出。但是,结果可能溢出ptrdiff_t的整数。或者,您可以将两个指针转换为类型intptr_t的整数,并减去整数以获得字节地址的差异。同样,从理论上讲,减法的结果可能会溢出intptr_t类型的整数。

答案 3 :(得分:1)

uint8_t * ptr = ...;
uint8_t * ptr2 = ptr + 5;

如果ptr100ptr2会是什么?正确,它将是105。但现在看看那段代码:

uint32_t * ptr = ...;
uint32_t * ptr2 = ptr + 5;

同样,如果ptr100ptr2会是什么?错误!它不会是105,而是120

为什么呢?指针算术不是整数运算!

ptr2 = ptr + 5;

实际上意味着:

ptr2 = int_to_ptr(ptr_to_int(ptr) + (sizeof(ptr) * 5));

函数int_to_ptrptr_to_int并不存在,我只是将它们用于演示目的,因此您可以更好地了解场景之间的情况。

因此,如果你减去两个指针,结果不是它们的地址差异,而是它们之间元素的数量:

uint32_t test[50];
ptrdiff_t diff = &test[20] - &test[10];

diff将为10,因为它们之间有10个元素(一个元素是一个uint32_t值)但这并不意味着test[10]和{之间有10个字节{1}},它们之间有40个字节,因为每个test[20]值占用4个字节的内存。

现在您可能会理解为什么减去不同类型的指针毫无意义,因为不同的类型具有不同的元素大小,然后这样的减法会返回什么?

如果你想要两个指针之间有多少字节,你需要将它们都转换为具有一个字节元素的数据类型(例如uint32_tuint8_t *可以工作)或者转换它们到char *(GNU扩展,但许多编译器也支持它),这意味着数据类型是未知的,因此元素大小也是未知的,在这种情况下,编译器将是字节大小的元素。所以这可行:

void *

还是这个

ptrdiff_t diff = (void *)ptr2 - (void *)ptr1;

更便携。

它将编译,它将提供结果。如果结果有意义,那就是另一个主题。除非两个指针指向相同的内存“对象”(相同的结构,相同的数组,相同的已分配的内存区域),否则它不是,正如标准所说,在这种情况下,结果是未定义的。这意味着ptrdiff_t diff = (char *)ptr2 - (char *)ptr1; 可以(合法地)具有任何值,因此在这种情况下,编译器也可以始终将diff设置为0,这将是标准所允许的。

如果您想要定义行为,请尝试改为:

diff

这是合法的和明确的。每个指针都可以转换为int值,ptrdiff_t diff = (ptrdiff_t)ptr2 - (ptrdiff_t)ptr1; 是一个int值,保证足够大,以便每个指针都可以适合它(不要使用ptrdiff_t或{{1为此目的,他们没有做出任何这样的保证!)。此代码将两个指针转换为整数,然后减去它们。我现在仍然没有看到你对int有什么用处,但是这段代码至少会提供一个定义的结果,但可能不是你可能期望的结果。

答案 4 :(得分:0)

尝试将每个地址进行类型转换为void *

long int distance = (void *)&a - (void *)&data[5];

正如其他人会指出的那样,这是危险的,未定义的,但如果你只是在探索记忆的运作方式,那应该没事。

答案 5 :(得分:0)

这是因为指针算术是关于偏移的。 例如,如果您有一个数组和指向该数组的指针,如:

$cookiesEnabled = true;

if (!isset($_COOKIE['mycookie'])) {
    $cookiesEnabled = false;
    if (!isset($_GET['cookie_test'])) {
        setcookie('mycookie', 1, 0, '/');
        @ob_end_clean();
        $_SESSION['original_url'] = $_SERVER['REQUEST_URI'];
        $uri = $_SERVER['REQUEST_URI'];
        $uri = explode('?', $uri);
        $q = (isset($uri[1]) && $uri[1])?explode('&', $uri[1]):array();
        $q[] = 'cookie_test=1';
        $uri[1] = implode('&', $q);
        $uri = implode('?', $uri);
        header('Location: '.$uri);
        die;        
    }
} else if (isset($_GET['cookie_test'])) {
    @ob_end_clean();
    $uri = $_SESSION['original_url'];
    unset($_SESSION['original_url']);
    header('Location: '.$uri);
    die;    
}

// if (!$cookiesEnabled) ... do what you want if cookies are disabled

然后你增加ptr,你期望数组的下一个值,例如数组[1]之后的数组[0],无论存储在哪种类型。因此,当你减去指针时,你不会得到例如字节,但偏移。

不要减去不属于同一数组的指针。

答案 6 :(得分:0)

int指针的大小和char指针的大小是不同的。在int size为4个字节的系统中,如果你将int_pointer ++,它将增加4个字节的地址,而在char_ptr的情况下,它将地址增加1个字节。因此,您可能会收到错误。