【问题标题】:Dereferencing a 50% out of bound pointer (array of array)取消引用 50% 的越界指针(数组数组)
【发布时间】:2015-11-13 00:47:37
【问题描述】:

这是我的“我不理解 C 和 C++ 中的指针”集合中的一个新问题。

如果我将两个具有相等值的指针的位(指向相同的内存地址)混合在一起,恰好具有完全相同的位表示,当一个是可取消引用的并且一个是结束时,标准是怎么说的应该发生吗?

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

// required: a == b
// returns a copy of both a and b into dest 
// (half of the bytes of either pointers)
int *copy2to1 (int *a, int *b) {
    // check input: 
    // not only the pointers must be equal
    assert (a == b);
    // also the representation must match exactly
    int *dest;
    size_t s = sizeof(dest);
    assert(memcmp(&a, &b, s) == 0); 

    // copy a and b into dest:
    // on "exotic" architectures, size does't have to be dividable by 2
    size_t half = s/2; // = floor(s/2), 
    char *pa = (char*)&a, *pb = (char*)&b, *pd = (char*)&dest;

    // copy half of a into dest:
    memcpy (pd, pa, half);
    // copy half of b into dest:
    memcpy (pd+half, pb+half, s-half); // s-half = ceil(s/2)

    //printf ("a:%p b:%p dest:%p \n", a, b, dest);    

    // check result
    assert(memcmp(&dest, &a, s) == 0);
    assert(memcmp(&dest, &b, s) == 0);

    return dest;
}

#define S 1 // size of inner array

int main(void) {
    int a[2][S] = {{1},{2}};
    int *past = a[0] + S, // one past the end of inner array a[0]
        *val = &a[1][0], // valid dereferenceable pointer
        *mix = copy2to1 (past, val);
    #define PRINT(x) printf ("%s=%p, *%s=%d\n",#x,x,#x,*x)
    PRINT(past);
    PRINT(mix);
    PRINT(val);
    return 0;
}

我真正想了解的是:“p指向对象x”是什么意思?

另见

这个问题是我之前关于数组数组的问题的一个更好的版本:

以及其他关于指针有效性的相关问题:

【问题讨论】:

  • @JohnBode "这让我头疼" 我希望如此。想到这里,我的脑袋都快要爆炸了,现在我希望每个人都有同样的感受。
  • 您能否补充一下使您相信标准委员会应该定义当您将两个指针混合在一起时会发生什么的原因?或者你只是在这里讨厌 C 和 C++ 吗?
  • ".. C 和 C++ 有问题。... 炒作..." 没问题。那是两种(不同的顺便说一句)语言的持续发展。它们并没有被宣传为可移植的程序集,它们在不牺牲两者都提供的高级抽象的情况下与您目前可以获得的一样可移植。哎呀,至少它们标准化的,据我所知,这不适用于 Java、C#、Python、Ruby、Go、Rust、JavaScript(尽管有 ECMA)、BASIC 方言,...
  • 是的,我确实读过它。我为什么要关心一种或另一种方式?这是一个糟糕的代码,在实际程序中没有位置。让语言律师讨论它的有效性,同时我们凡人无论如何都应该避开这种结构。如果您认为此类问题正在扼杀 C 和 C++,那么您需要更加努力地思考。
  • @curiousguy 是的。您对两种不同的语言提出相同的问题。这条规则的重点是,许多人标记垃圾邮件 C 和 C++ 以吸引更多观众,但随后对 C 答案不感兴趣,因为他们使用 C++ 或其他方式编程。这就是为什么我对标记为 C 和 C++ 的问题非常敌对的原因。只有询问 C 和 C++ 之间的差异或交互的问题才应同时标记。

标签: c++ c arrays pointers language-lawyer


【解决方案1】:

在 [basic.compound] 中:

如果 T 类型的对象位于地址 A,则 cv T* 类型的指针其值为 据说地址A 指向该对象,无论该值是如何获得的

pastval 具有相同的地址,因此它们指向同一个对象。一个是第一行的“结束”,第二个是第二行的第一个元素并不重要。在那个地址有一个有效的对象,所以这里的一切都是完全合理的。


在 C++17 中,从 P0137 开始,这发生了很大变化。现在,[basic.compound] 将指针定义为:

指针类型的每个值都是以下之一:
指向对象或函数的指针(指针被称为指向对象或函数),或
一个超过对象末尾的指针(5.7),或者
— 该类型的 空指针值 (4.11),或
无效的指针值

所以现在,past 是第二种类型的值(一个指向末尾的指针),但val 是第一种类型的值(指向的指针)。这些是不同类别的值,不具有可比性:

指针类型的值是指向或超过对象末尾的指针,表示该对象占用的内存(1.7)中的第一个字节的地址或占用的存储结束后的内存中的第一个字节的地址分别由对象。 [注意:超过对象(5.7)末尾的指针不被认为指向可能位于该地址的对象类型的不相关对象。指针值在其表示的存储达到其存储持续时间结束时变为无效;见 3.7。 ——尾注]

past 不指向某物,因此将其内容视为与val 相同不再有意义。

【讨论】:

  • 那我可以用a[0][size]来指代a[1][0]吗?
  • @curiousguy 是的。 &amp;a[0][size] 是地址 a + size * sizeof(T)&amp;a[1][0] 是地址 a + 1 * (size * sizeof(T)) + 0。这些是相同的地址,因此当您取消引用它时,您会得到相同的值。
  • 如果结果指向数组对象的最后一个元素之后,则不应将其用作被评估的一元 * 运算符的操作数(C99 6.5 .6 加法运算符)。 C 和 C++ 似乎在这里有所不同。
  • @Barry:声明int a[3][5] 创建了一个对象a,其中包含三个较小的对象,每个对象都是一个由五个整数组成的数组;它还包含一个叠加在上面的 15 个整数的数组。将a 直接用作右值会产生指向三个五元素内部数组中的第一个的指针,而使用a[0] 作为右值将产生指向该五元素内部数组中的第一个 int 的指针。将a 转换为int*int[] 将产生一个指向叠加的15 元素数组的第一个元素的指针。编译器不需要跟踪指针是如何派生的......
  • ...但他们被允许这样做。指针a[0]+5a[1] 将比较相等,但前者是a[0] 的过去指针,第二个是指向a[1] 第一个元素的指针。如果标准要求对指针的某些操作必须使编译器“忘记”它可能对其行为施加的任何限制,那将很有帮助,但在这些问题上却相当模糊。
【解决方案2】:

我真正想明白的是:“p指向对象x”是什么意思。

对象p 包含一个值,该值对应于对象x 在内存中的位置。

就是这样。这就是它的全部含义。您似乎决心使这比需要的更复杂。

指针类型不是算术类型,也不能像那样随意修改。通过在左值上使用一元 &amp; 运算符、使用不是 sizeof 或一元 &amp; 运算符的操作数的数组表达式或调用返回指针值的库函数来获得有效的指针值。

除此之外的所有内容(大小、表示、物理与虚拟等)都是实现细节,并且在以下情况下,实现会有很大的不同它涉及到表示地址。这就是为什么标准没有说明任何当您使用指针值玩 Frankenstein 博士时会发生什么。

如果您非常熟悉平台的寻址约定(虚拟和物理),并且您知道您的实现如何在内存中布置项目以及它如何表示指针类型,并且您有一个有效的用例 em> 以这种方式破解您的指针值,然后破解您的内心 - 两种语言标准都没有关于这个主题的内容。

【讨论】:

  • "获得了有效的指针值 (...)" 你是说我不能在符合标准的程序中的指针上使用memcpy?跨度>
  • @curiousguy:将有效指针值的内容复制到另一个相同类型的对象,当然可以,尽管p = q; 也同样简单。如果类型不兼容并且您尝试使用复制到的指针,那么行为将是未定义的(这意味着它可能工作得很好,它可能会彻底崩溃,它可能会在以后导致运行时错误,等等)。指向不同类型的指针不必具有相同的大小或表示。
  • 是的:我的程序中只有兼容的类型(只有int*)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-25
  • 1970-01-01
  • 2011-10-20
  • 2013-07-27
相关资源
最近更新 更多