订阅此指针的效果

时间:2015-03-07 22:45:49

标签: c++

this[5]做什么?我是否调用了某种未定义的行为?怎么样:

std::vector<decltype(this)> foo{this, this + 5u};

这有用吗?我想知道指针运算对this的影响是什么。这是一个测试程序:

#include <iostream>
#include <vector>

struct Foo
{
    int n = 1;

    void foo()
    {
        std::vector<decltype(this)> foo{this, this + 5u};
        for (decltype(foo.size()) i = 0; i != foo.size(); ++i)
        {
            std::cout << foo[i]->n << "\n";
        }
    }
};

int main()
{
    Foo{}.foo();
}

/* OUTPUT:
 * 1
 * 0
 */

3 个答案:

答案 0 :(得分:3)

首先,您需要回想一下,指针上的下标本质上是语法糖,旨在使数组更容易操作。反过来说,它是通过对指向这些数组元素的指针进行算术来实现的。

因此,给定int array[3]和指针int* ptr = &array[0]ptr[2]是指向array的第3个元素的指针。

出于同样的原因,并且由于数组的名称如何衰减为指针,array[2]是指向array的第3个元素的指针。

您甚至可以更改“起点”:给定int* ptr = &array[1]ptr[1]也是指向array第3个元素的指针,因为您实际上是在写(array+1+1) }。

没有理由不能将相同的逻辑应用于名为this的指针。但是,如果并且只有对象被分配为数组的一部分,并且您没有尝试读取超出该数组的边界,那么它的定义很明确。

以下是一个例子:

#include <iostream>

struct T
{
   int x;

   T(int x) : x(x) {};
   void bar() { std::cout << x << std::endl; }
   void foo() { this[1].bar(); }  // or (this+1).bar()
};

int main()
{
   T array[4] = { T(0), T(1), T(2), T(3) };
   array[0].foo();  // OK, outputs 1
   array[1].foo();  // OK, outputs 2
   array[2].foo();  // OK, outputs 3
   array[3].foo();  // undefined; `this[1]` is the same as `array[4]`, so
                    //   evaluating that pointer has UB, never mind invoking
                    //   bar() through it and printing a member variable!
}

这是相关的标准措辞:

  

[C++11: 5.2.1/2]:后缀表达式后跟方括号中的表达式是后缀表达式。其中一个表达式应具有“指向T”的类型,另一个表达式应具有未映射的枚举或整数类型。结果是类型为“T的左值”。类型“T”应是完全定义的对象类型。表达式E1[E2]*((E1)+(E2)) 相同(根据定义)[注意:有关*+以及8.3的详情,请参阅5.3和5.7。 4有关数组的详细信息。 -end note]

     

[C++11: 5.7/5]:当一个具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。 [..]

答案 1 :(得分:1)

this的非静态成员函数中的

C是类型C * const的特殊变量,它指向类实例&#34; start&#34;在记忆中。

使用this的指针算法由与通常指针相同的规则定义,所以

this[5]

类似于

struct T
{
  ...
};

T t[10];

t[5]; // this

- 它访问&#34;数组&#34;或

的第6个元素
*(C *)((void *)this + sizeof(C) * 5)

一般来说,这对this没有意义。

在以下代码中:

std::vector<decltype(this)> foo{this, this + 5u};

定义C *指针的向量并用以下内容初始化它们:

{(void *)this, (void *)this + sizeof(C) * 1, (void *)this + sizeof(C) * 2, (void *)this + sizeof(C) * 3, (void *)this + sizeof(C) * 4}

一般来说,这也没有意义。

如果C是&#34;简单&#34; type(POD或没有父类/派生类),然后当一堆C个实例是连续内存时(例如在数组或vector中),那么你可以这样访问(这[1])相邻元素。

但是如果你的类是复杂层次结构的一部分,那么this[1]可能指向一些与C类型不对齐的地方,因为复杂的层次结构类在内存中以非平凡的方式对齐。 / p>

更新:问题已使用示例

进行了更新

在这个例子中:

struct Foo
{
    int n = 1;

    void foo()
    {
        std::vector<decltype(this)> foo{this, this + 5u};
        for (decltype(foo.size()) i = 0; i != foo.size(); ++i)
        {
            std::cout << foo[i]->n << "\n";
        }
    }
};

int main()
{
    Foo{}.foo();
}

Foo是POD类型,根据对齐选项,它很可能在内存中占用4,8或16个字节,系统是32位或64位。让我们假设sizeof(Foo) == 4

Foo{}将在堆栈上创建Foo实例。然后将调用foo()方法。

内部foo()方法this将指向堆栈内存中的Foo实例启动。让我们假设它是0xAABBCC00。

这一行:

std::vector<decltype(this)> foo{this, this + 5u};

将创建Foo *的向量,并使用{0xAABBCC00, 0xAABBCC04, 0xAABBCC08, 0xAABBCC0C}初始化它。

然后你迭代foo

        for (decltype(foo.size()) i = 0; i != foo.size(); ++i)
        {
            std::cout << foo[i]->n << "\n";
        }

foo[0]this,所以一切都会好的。

foo[1]->n (Foo *)((void *)0xAABBCC04)->n只是*(int *)(Foo *)((void *)0xAABBCC04),因为n位于Foo结构的开头 - 这是读取一些非直接初始化的内存与this相邻的。 对foo[2]->foo[3]->foo[4]->的读取和读取具有未定义的行为,通常会导致读取一些垃圾或分段错误。

答案 2 :(得分:0)

指针算术假设它正在处理数组。给定数组任何类型t

t x[6];

然后x+5等于&x[5]

因此,this + 5u假设*this是一个数组元素(或std::vector),至少有5个后续元素,并给出第六个元素的地址(因为数组)索引从零开始。)

如果*this不是数组的元素,后跟至少5个元素,那么评估this + 5以及任何后续尝试迭代向量上的元素并取消引用指针将具有未定义的行为。< / p>

我们假设您有一个成员函数来执行此操作;

void Foo::something()
{
    std::vector<decltype(this)> foo{this, this + 5u};

    for (auto &iter: foo)
         iter->ChangeMe();     //  member function of Foo that changes something
}

然后像这样使用它

Foo x[6];
x[0].something();    // okay ... will iterate over all elements of x
x[5].something();    // undefined behaviour, since the loop will fall off the end of x

是否有用取决于你在做什么。我建议不要......对象需要访问包含它的数组(或容器)的元素是很少见的。