关于指针之间的转换



什么才是真正的"指针之间的转换";意思是如果我有类似type_1* x, type_2* y的东西并调用dynamic_cast<type_1*>(y)(问题不是关于动态转换,我使用它是因为据我所知,它是一个可以很好地使用指针转换的函数),它会返回什么?指向与y(当然是NULL)相同对象的*type_1类型的指针?这是真的吗?为了理解指针之间的这些铸件,我还有什么需要了解的吗?

在类和多个继承的情况下,强制转换指针也可以更改指针的值(地址)。

观察:

#include <iostream>
class Foo {
int foo;
};
class Bar {
double bar;
};
class Baz: public Foo, public Bar {
short baz;
};
int main() {
Baz bz;
std::cout << &bz << " " << static_cast<Bar *>(&bz) << " " << static_cast<Foo *>(&bz) << std::endl;
}

这是因为父类型的实例位于派生对象的不同偏移处,它们不能都位于同一地址。

这是向上转换(子级到父级),它总是合法的,static_cast就足够了,对于向下转换(父级到子级),需要对dynamic_cast进行额外的检查。

什么实际上是"指针之间的转换";意思是

它意味着从一种类型的指针转换为另一种指针类型。

[dynamic_cast<type_1*>(y)]将返回什么?类型为*type_1的指针指向与y相同的对象(当然是NULL)?这是真的吗?

这是真的。";"相同对象";在面向对象的意义上是正确的。尽管从严格的C++意义上讲,不同类型的指向对象可能被认为是不同的对象。

为了理解指针之间的这些转换,我还有什么需要了解的吗?

是的,这不是与指针和转换相关的全部规则。为了理解指针和转换,我建议彻底阅读文档,并可能阅读一些C++书籍。

不久前,我看到了控制第一次阿波罗任务的代码。这都是汇编,C,单独的C++,在那些日子里是不存在的。我们可以想象,在那个程序集中,他们所拥有的只是必须手动管理的寄存器和原始内存。

在包括C/C++在内的现代非汇编语言中,编译器将您从手动内存管理的负担中解放出来。命名变量的内存,如

int x = 0;

自动分配和解除分配。此外,您不需要为临时需要的内存而烦恼,例如,在一些复杂的计算过程中。在C/C++中;通常的";变量附带有指针,允许您间接访问自动或半自动管理的内存。此外,您需要指针来访问堆分配的("无名称")内存(还记得operator new吗?)。这几乎是你应该知道的关于他们的全部。语法是已知的:星号*;箭头";8和电流和CCD_ 9。编译器的职责是保护您不在指针上进行可能愚蠢的操作,比如把它们当作整数来使用(20世纪70年代流行的技巧),或者把它们当作指向您声明的其他东西来使用。声明一个指针意味着你声明了对它的全部操作。为了防止你的脚被意外绊倒,编译器禁止像自动分配不同类型的指针这样可怕的事情。

但是,是编译器的大师,而不是相反,因此您可以完全控制。这就是指针转换的步骤。一方面,通过执行指针转换,您可以暂时关闭编译器创建者为您准备的所有安全措施。另一方面,有了合适的演员阵容,你就可以保证你的节目是正确的!

真正重要的问题是:哪些涉及指针转换的操作是安全的?

首先,有点令人惊讶的是:有几种类型的指针,它们不需要相互兼容:

  • 指向数据的指针
  • 指向(自由)函数的指针
  • 指向数据成员的指针
  • 指向成员函数的指针
  • void*指针

它们的内部表示,甚至它们所占的位数,都不必相同!

那么,什么是允许的呢?

C语言喜欢使用void*作为传递任意类型数据的手段。因此,您可以确保您可以将任何数据指针转换为void*,然后再转换回其原始类型,这将起作用。与指向函数的指针类似。

指针转换自然发生的另一个地方是低级序列化。C和C++中的许多函数都在声明为char*const char*的缓冲区上工作。由此可以推断,您应该能够将任何指向数据的指针转换为char*,并且它将按预期工作。

第三种常见的指针转换是在处理继承时,尤其是在具有虚拟函数的类的上下文中。这里来了CCD_ 16以在潜在的";危险的";沿着继承树。

还有其他安全的强制转换,以及可能导致未定义行为甚至分段错误的强制转换。例如,指向int的指针通常被转换到指向float的指针或从指向CCD_ 18的指针转换,并且它通常工作,例如,在AVX或GPU编程中。这些特定类型转换的正确性是由硬件供应商而不是C++标准来保证的。

如果你离开安全铸造的土地,会有什么危险?例如,将指向函数的指针强制转换为void*,然后将此指针强制转换成char*。如果你试图取消引用这个指针,更不用说通过它写了,你的程序注定会立即失效。同样危险的转换也发生在指向数据的指针和指向成员的指针之间。不要那样做。此外,不要使用指针将指针强制转换为整数等。这通常是未定义行为的领域,除非你仔细查阅了标准并确定你所做的事情是安全的。

综上所述:指针转换通常不会生成代码,但有时会生成。请参阅以下示例。

示例

1.继承。静态铸造与动态铸造

假设我们有从XY导出的XY

struct X
{
float x;
};
struct Y
{
float y;
};
struct XY : public X, public Y
{
float xy;
};

然后这个代码:

XY a;
std::cout << static_cast<X*>(&a) << "n";
std::cout << static_cast<Y*>(&a) << "n";
std::cout << reinterpret_cast<X*>(&a) << "n";
std::cout << reinterpret_cast<Y*>(&a) << "n";
std::cout << &a << "n";

可能会产生以下结果:

0x7fffc7d75dcc
0x7fffcc7d75dd0
0x7fffc 7d75DCc
0X7fffc7d 75dcc
0x7 fffc7d 75 dcc

因此,static_cast确实改变了";值";指针的!同时CCD_;重新解释";存储在指针变量/寄存器中但不修改它们的位。

有趣的是,如果我们在Y中定义了一个虚拟方法,那么相同的代码将揭示XYXY:中的相反顺序

0x7ffc6a5764c
0x7ffC6a57640
0x7ffC6 a576400x7ff c6a57640
0x7 ffc6a47640

因此,在多重继承中嵌入基类的顺序似乎是未定义的/依赖于实现的。

2.强制转换为整数类型该标准允许向长度足够的整数类型强制转换指针。

铸造";C样式指针";到整数类型按预期工作:

XY a;
std::cout << std::hex << reinterpret_cast<uint64_t>(&a) << "n";
std::cout << reinterpret_cast<void*>(&a) << "n";

例如,

7fff84f5a6cc
0x7fff84 f5a6Cc

如预期。但他的标准并不能保证这一点。

3.指向数据成员的指针这种情况更有趣:指向成员的指针不能强制转换为任何内容。但有一个变通办法:

XY a;
float XY::* mptr_xy = &XY::xy;
float XY::* mptr_x = &XY::x;
float XY::* mptr_y = &XY::y;
std::cout << *reinterpret_cast<uint64_t*>(&mptr_x) << "n";
std::cout << *reinterpret_cast<uint64_t*>(&mptr_y) << "n";
std::cout << *reinterpret_cast<uint64_t*>(&mptr_xy) << "n";

这可能会产生以下结果:

0
4
8

这表明指向成员的指针是指向对象内存的偏移量,而不是指向RAM的指针。如果我们在Y中添加一个虚拟析构函数,我们可能会得到:

12
8
16

偏移量发生了变化,为vptr腾出了空间,并将Y移到了前面。如果XY都有虚拟析构函数,可能的结果是:

8
24
28

结论:指向成员的指针与标准指针完全不同。

4.动态投射

假设XY都有虚拟析构函数。此代码:

XY xy;
Y y;
std::cout << "xy:n";
std::cout << &xy << "n";
std::cout << dynamic_cast<Y*>(&xy) << "n";
std::cout << dynamic_cast<Y*>(dynamic_cast<X*>(&xy)) << "n";
std::cout << dynamic_cast<X*>(&xy) << "n";
std::cout << "y:n";
std::cout << &y << "n";
std::cout << dynamic_cast<XY*>(&y) << "n";
std::cout << dynamic_cast<Y*>(dynamic_cast<XY*>(&y)) << "n";
std::cout << dynamic_cast<Y*>(reinterpret_cast<XY*>(&y)) << "n";
std::cout << dynamic_cast<Y*>(static_cast<XY*>(&y)) << "n";

可能产生以下输出:

xy:
0x7ffc0a570590
0x7ffc 0a57050
0x7ffc0a570590
y:
0

  • xy的地址的转换可以给出两个不同的结果
  • 对CCD_ 38地址的转换可以给出3种不同的结果

摘要:
C++中的指针强制转换比C中的要复杂得多。标准只保证行为,但不保证实现。因此,例如,转换为整数类型可能涉及一些位操作——现有的实现不这样做是因为这样做没有意义,而不是因为它被禁止。主要的差异源于C++具有多个继承、虚拟函数和指向成员的指针。

洞穴这个长回复并没有涵盖指针强制转换的所有方面,尤其是数据对齐。此外,该标准没有对各种指针的实现施加任何限制,甚至reinterpret_cast也没有保证保留内部位表示:

与static_cast不同,但与const_cast一样,interpret_cast表达式不编译为任何CPU指令(除非在整数和指针之间转换,或者在指针表示取决于其类型的模糊体系结构上)。

https://en.cppreference.com/w/cpp/language/reinterpret_cast

另请参阅:

  • https://en.cppreference.com/w/cpp/language/explicit_cast
  • https://en.cppreference.com/w/cpp/language/const_cast
  • https://en.cppreference.com/w/cpp/language/dynamic_cast
  • https://en.cppreference.com/w/cpp/language/static_cast
  • https://en.cppreference.com/w/cpp/language/implicit_conversion

指针之间的强制转换意味着什么?

更改同一数据/对象的含义。

示例1:

class Base {
public:
void food() {printf("foon");}
}
class Derived : public Base {
public:
void bar() {printf("bar");}
}
int main() {
Base * base = new Derived();
base->foo(); // OK, it compiles
base->bar(); // **NOK, syntax error**, even if you instantiated Derived class
Derived * derived = static_cast<Derived *>(base);
derived->foo(); // OK, it compiles
derived->bar(); // OK, it compiles
return 0;
}

示例2:

int main() {
char word[] = {'W', 'o', 'r', 'd', '', '', '', ''}; // array of 8 chars
const char * ptrChar = static_cast<const char *>(word); // cast to "const char* "
const int * ptrInt = reinterpret_cast<const int *>(word); // cast to "const int* "
int lenChar = 0
while (ptrChar[lenChar] != 0)
++ lenChar;
printf("lenChar = %dn", lenChar); // lenChar = 4
int lenInt = 0;
while (ptrInt[lenInt] != 0)
++ lenInt;
printf("lenInt = %dn", lenInt); // lenInt = 1
return 0;
}

等等。。。。你应该"播放";指针越多,你就会对指针和转换感有更多的了解。这是C/C++中的一个重要主题

动态铸造更为复杂。它需要了解运行时和编译时、多态性、层次结构等。在这两个例子中,我没有使用动态转换,因为它不起作用。

最新更新