【问题标题】:Pointer Arithmetic [closed]指针算术[关闭]
【发布时间】:2010-09-28 12:42:00
【问题描述】:

有没有人有关于指针运算的好文章或解释(博客、示例)?图听众是一群学习C和C++的Java程序员。

【问题讨论】:

标签: c pointers pointer-arithmetic


【解决方案1】:

这是我学习指针的地方:http://www.cplusplus.com/doc/tutorial/pointers.html

一旦你理解了指针,指针算术就很容易了。它与常规算术之间的唯一区别是您添加到指针的数字将乘以指针指向的类型的大小。例如,如果您有一个指向 int 的指针,而 int 的大小为 4 个字节,则 (pointer_to_int + 4) 将计算为提前 16 个字节(4 个整数)的内存地址。

所以当你写的时候

(a_pointer + a_number)

在指针算术中,真正发生的是

(a_pointer + (a_number * sizeof(*a_pointer)))

在常规算术中。

【讨论】:

  • 但是不能再添加一个指针地址。为什么
  • 链接到的文章很棒,现在更有意义了!
  • @MartinErhardt,你可以喜欢这个 p = (T*)(((long)p) + ((long)p2))
【解决方案2】:

首先,binky 视频可能会有所帮助。这是一个关于指针的好视频。对于算术,这里是一个例子:

int * pa = NULL;
int * pb = NULL;
pa += 1; // pa++. behind the scenes, add sizeof(int) bytes
assert((pa - pb) == 1);

print_out(pa); // possibly outputs 0x4
print_out(pb); // possibly outputs 0x0 (if NULL is actually bit-wise 0x0)

(请注意,严格递增包含空指针值的指针是未定义的行为。我们使用 NULL 是因为我们只对指针的值感兴趣。通常,仅在指向的元素时使用递增/递减一个数组)。

下面展示了两个重要的概念

  • 对指针加/减整数意味着将指针向前/向后移动 N 个元素。因此,如果一个 int 是 4 字节大,pa 在我们的平台上可能包含 0x4 后递增 1。
  • 另一个指针减去一个指针意味着得到它们的距离,由元素测量。所以从 pa 中减去 pb 将得到 1,因为它们有一个元素距离。

举一个实际的例子。假设您编写了一个函数,人们为您提供了一个开始和结束指针(在 C++ 中很常见):

void mutate_them(int *begin, int *end) {
    // get the amount of elements
    ptrdiff_t n = end - begin;
    // allocate space for n elements to do something...
    // then iterate. increment begin until it hits end
    while(begin != end) {
        // do something
        begin++;
    }
}

ptrdiff_t 是 (end - begin) 的类型。对于某些编译器,它可能是“int”的同义词,但对于另一个编译器可能是另一种类型。无法知道,所以选择通用 typedef ptrdiff_t

【讨论】:

  • 需要注意的是 定义了ptrdiff_t(根据标准)。我想说,它不是一个特殊类型,只是编译器吐出的类型的名称(typedef)。
  • 是的,一定是有符号整数类型。不想过多介绍细节。
  • 讨厌指出这一点,但您的第一个示例是未定义的行为。 ;) 你不能增加一个空指针。 :)
  • jalf,感谢您指出这一点。我添加了一个关于它的注释:) 现在我的说法“如果 NULL 实际上是按位 0”被打破了,因为 0 是一个零整数。但我想你知道我的意思呵呵
  • 我通常觉得很难按照视频教程进行操作,但是这个笨拙的教程超级简单、很棒而且很有趣 :)。只需 3 分钟即可掌握基础知识。
【解决方案3】:

应用NLP,称之为地址算术。 '指针'之所以令人恐惧和误解,主要是因为它们是由错误的人教的和/或在错误的阶段以错误的方式使用错误的例子。难怪没有人“得到”它。

在教授指针时,教师会继续说“p 是指向 a 的指针,p 的值是 a 的地址”等等。它只是行不通。这是供您建造的原材料。用它练习,你的学生就会明白。

'int a',a是一个整数,它存储整数类型的值。 'int* p',p是一个'int star',它存储'int star'类型值。

'a' 是如何获取存储在 a 中的 'what' 整数(尽量不要使用 'value of a') '&a' 是您获取 a 本身存储的“位置”的方式(尝试说“地址”)

'b = a' 为此,双方必须是相同的类型。如果 a 是 int,则 b 必须能够存储 int。 (所以______ b,空白处用'int'填充)

'p = &a' 为此,双方必须是相同的类型。如果 a 是整数,&a 是地址,则 p 必须能够存储整数的地址。 (所以______ p,空白处用'int *'填充)

现在用不同的方式写 int *p 来带出类型信息:

int* | p

什么是'p'?回答:它是'int *'。所以'p'是一个整数的地址。

int | *p

什么是'*p'?答:它是一个'int'。所以 '*p' 是一个整数。

现在开始地址算术:

int a; a=1; a=a+1;

我们在 'a=a+1' 中做了什么?将其视为“下一个”。因为 a 是一个数字,这就像说“下一个数字”。由于 a 持有 1,因此说“下一步”将使其成为 2。

// 错误的例子。你被警告了!!! 整数 *p 诠释一个; p = &a; p=p+1;

我们在“p=p+1”中做了什么?它仍然在说“下一步”。这一次,p 不是数字而是地址。所以我们所说的是“下一个地址”。下一个地址取决于数据类型,更具体地说取决于数据类型的大小。

printf("%d %d %d", sizeof(char), sizeof(int), sizeof(float));

所以地址的“下一个”将向前移动 sizeof(data type)。

这对我和我曾经教过的所有人都有效。

【讨论】:

  • 不同意“下一个”。不清楚下一个指针是否在 4b 之外,或者它是否指向可以指向的下一个 4b(重用之前的低 3b)。当然,这很少使用,但这里给出的类比对我不起作用。
  • 这其实是一个非常明确的答案。我真的很喜欢直观地解释 "*p" 是一个整数的部分。
【解决方案4】:

我认为指针算术的一个很好的例子是以下字符串长度函数:

int length(char *s)
{
   char *str = s;
   while(*str++);
   return str - s;
}

【讨论】:

  • 示例中没有显示指针不总是递增 1 字节,这经常被遗漏。
【解决方案5】:

所以,要记住的关键是指针只是一个字大小的变量,它是为解除引用而键入的。这意味着无论是void *,int *,long long **,它仍然只是一个字大小的变量。这些类型之间的区别在于编译器认为取消引用的类型。澄清一下,字长意味着虚拟地址的宽度。如果你不知道这是什么意思,只要记住在 64 位机器上,指针是 8 个字节,而在 32 位机器上,指针是 4 个字节。地址的概念对于理解指针非常重要。地址是能够唯一标识内存中某个位置的数字。内存中的所有东西都有一个地址。出于我们的目的,我们可以说每个变量都有一个地址。这不一定总是正确的,但编译器让我们假设这一点。地址本身是字节粒度的,这意味着 0x0000000 指定内存的开头,而 0x00000001 是内存中的一个字节。这意味着通过向指针添加一个,我们将一个字节向前移动到内存中。现在,让我们使用数组。如果你创建一个 quux 类型的数组,它有 32 个元素大,它将从分配的开始到分配的开始加上 32*sizeof(quux),因为数组的每个单元都是 sizeof(quux) 大的。因此,实际上,当我们用 array[n] 指定数组的元素时,这只是 *(array+sizeof(quux)*n) 的语法糖(简写)。指针算法实际上只是更改您所指的地址,这就是为什么我们可以使用 strlen 实现

while(*n++ != '\0'){
  len++;
}

因为我们只是逐字节扫描,直到我们达到零。希望对您有所帮助!

【讨论】:

    【解决方案6】:

    有几种方法可以解决它。

    大多数 C/C++ 程序员认为的直观方法是指针是内存地址。 litb 的示例采用了这种方法。如果你有一个空指针(在大多数机器上对应于地址 0),并且你添加一个 int 的大小,你会得到地址 4。这意味着指针基本上只是花哨的整数。

    不幸的是,这存在一些问题。首先,它可能行不通。 不能保证空指针实际使用地址 0。(尽管将常量 0 分配给指针会产生空指针)。

    此外,不允许增加空指针,或者更一般地说,指针必须始终指向分配的内存(或过去的一个元素),或者特殊的空指针常量 0。

    因此,更正确的想法是指针只是迭代器,允许您迭代分配的内存。 这确实是 STL 迭代器背后的关键思想之一。它们被建模为非常像指针,并提供修补原始指针以作为适当的迭代器工作的特化。

    例如,here 对此给出了更详细的解释。

    但是后一种观点意味着你应该真正解释 STL 迭代器,然后简单地说指针是这些迭代器的一个特例。您可以增加一个指针以指向缓冲区中的下一个元素,就像您可以使用 std::vector<int>::iterator 一样。它可以指向数组末尾的一个元素,就像任何其他容器中的结束迭代器一样。你可以减去两个指针指向同一个缓冲区来得到它们之间的元素数量,就像你可以使用迭代器一样,就像迭代器一样,如果指针指向不同的缓冲区,你可以没有有意义地比较它们。 (举个实际的例子说明为什么不这样做,考虑在分段的内存空间中发生了什么。指向不同段的两个指针之间的距离是多少?)

    当然,在实践中,CPU 地址和 C/C++ 指针之间存在非常密切的相关性。但它们完全不是一回事。指针有一些限制,在您的 CPU 上可能不是绝对必要的。

    当然,大多数 C++ 程序员在第一次理解时都会蒙混过关,即使它在技术上是不正确的。它通常与您的代码最终表现得足够接近,以至于人们认为他们得到了它,然后继续前进。

    但是对于来自 Java 并且刚刚从头开始学习指针的人来说,后一种解释可能同样容易理解,并且以后会给他们带来更少的惊喜。

    【讨论】:

    • 注意指针并不总是 4 字节长,这一点很重要。在某些系统(64 位)上,它可能是 8 个字节长。例如,永远不要假设指针与整数大小相同。由于程序员将指针隐藏在整数中,已经犯了太多错误!
    • 没错,这很重要。我打算将 4 字节的东西作为一个示例,但我可能应该更清楚地说明这一点,因为指针肯定保证是 4 字节,而且它们通常不是。
    【解决方案7】:

    这是一个很好的链接here关于指针算法

    例如:

    指针和数组

    计算ptr + i 的地址的公式,其中ptr 的类型为T *。那么地址的公式是:

    addr( ptr + i ) = addr( ptr ) + [ sizeof( T ) * i ]

    对于32位平台的int类型,addr(ptr+i) = addr(ptr)+4*i;

    减法

    我们还可以计算 ptr - i。例如,假设我们有一个名为 arr 的 int 数组。 诠释 arr [ 10 ] ; 整数 * p1, * p2 ;

    p1 = arr + 3 ; // p1 == & arr[ 3 ] 
    p2 = p1 - 2 ; // p1 == & arr[ 1 ] 
    

    【讨论】:

    • 这可能是真的——不能保证 int 在 32 位平台上是 32 位宽的——规则是它必须能够表示 [−32767,+32767],这只是意味着它必须至少为 16 位宽。您可能有一个 32 位平台,其中编译器将整数视为 16 位或 64 位宽。
    • 循环定义@Clearer - 通常当我们谈论平台的“位宽”时,我们指的是int 的大小(应该是“自然的”整数类型)。
    • @TobySpeight “通常”无关紧要。
    猜你喜欢
    • 2015-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多