You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

reinterpret_cast何时违反C++标准?自定义栈实现是否合法?

闲置内存栈式管理的C++代码合规性问题

我写了一段代码,用栈结构管理闲置内存,按需分配(客户特殊需求),实现如下:

void* stack = nullptr;

template <typename P>
void push(P* p) {
  // 将*p的内存空间重新解释为存储栈顶元素地址的空间
  *reinterpret_cast<void**>(p) = stack;
  // 现在*p是栈顶元素,更新stack指针指向它
  stack = p;
}

template <typename P>
P* pop() {
  if (stack == nullptr) throw;

  // 获取栈顶元素地址
  P* p = static_cast<P*>(stack);
  // stack应该指向*p之后的下一个元素,该地址存储在*p的内存空间中
  stack = *reinterpret_cast<void**>(stack);
  return p;
}

这里stack是指向栈顶元素的指针,新内存入栈时,其前8字节用于存储前一个栈顶元素的地址,同时更新stack指针,形成简单的链式栈结构。

之前实现时编译器多次报错:assignment to cast is illegal, lvalue casts are not supported,我知道部分内存操作不符合C++标准,现在咨询两个问题:

  1. 当前的代码实现是否符合C++标准?
  2. reinterpret_cast在哪些情况下会违反C++标准?

代码运行示例及输出

结构体定义

#include <iostream>
#include <bitset>

constexpr size_t bytes_8  = 8 * 8;
constexpr size_t bytes_20 = 8 * 20;
constexpr size_t bytes_50 = 8 * 50;

// 假设std::bitset的开头是连续存储的数据

struct A {
  std::bitset<bytes_8> bits;
};

struct B {
  std::bitset<bytes_20> bits;
};

struct C {
  std::bitset<bytes_50> bits;
};

主函数代码

int main() {
  // 假设它们是在堆上分配的:
  A a;  B b;  C c;

  std::cout << "Addresses" << '\n'
            << "a: " << &a << "\t"
            << "b: " << &b << "\t"
            << "c: " << &c
            << std::endl << std::endl << std::endl;

  push<A>(&a);
  std::cout << "---------- push<A>(&a) ----------\n"
            << "'a' is the top element (and the only one), so 'stack' points to it,\n"
            << "meanwhile 'a' stores the address of the previous element (none)\n\n"
            << "stack: " << stack << "\ta.bits: " << std::hex << a.bits.to_ullong()
            << std::endl << std::endl;

  push<B>(&b);
  std::cout << "---------- push<B>(&b) ----------\n"
            << "now 'b' is the top element, so 'stack' updates to point to it,\n"
            << "'b' stores the address of the previous element (a)\n\n"
            << "stack: " << stack << "\tb.bits: " << std::hex << b.bits.to_ullong()
            << std::endl << std::endl;

  push<C>(&c);
  std::cout << "---------- push<C>(&c) ----------\n"
            << "finally 'c' is the top element and 'stack' points to it,\n"
            << "'c' stores the address of the previous element (b)\n\n"
            << "stack: " << stack << "\tc.bits: " << std::hex << c.bits.to_ullong()
            << std::endl << std::endl << std::endl;


  auto p1 = pop<C>();
  std::cout << "---------- pop<C>() ----------\n"
            << "'b' is the top element and the next one after it is 'a'\n\n"
            << "ret: " << p1 << "\nstack: " << stack << "\tb.bits: " << std::hex << b.bits.to_ullong()
            << std::endl << std::endl;

  auto p2 = pop<B>();
  std::cout << "---------- pop<B>() ----------\n"
            << "'a' is the top element and the last one in the stack\n\n"
            << "ret: " << p2 << "\nstack: " << stack << "\ta.bits: " << std::hex << a.bits.to_ullong()
            << std::endl << std::endl;

  auto p3 = pop<A>();
  std::cout << "---------- pop<A>() ----------\n"
            << "the stack is empty\n\n"
            << "ret: " << p3 << "\nstack: " << "0x" << stack
            << std::endl << std::endl;

  return 0;
}

输出结果

Addresses
a: 0x7fffffffdc20       b: 0x7fffffffdc40       c: 0x7fffffffdc60


---------- push<A>(&a) ----------
'a' is the top element (and the only one), so 'stack' points to it,
meanwhile 'a' stores the address of the previous element (none)

stack: 0x7fffffffdc20   a.bits: 0

---------- push<B>(&b) ----------
now 'b' is the top element, so 'stack' updates to point to it,
'b' stores the address of the previous element (a)

stack: 0x7fffffffdc40   b.bits: 7fffffffdc20

---------- push<C>(&c) ----------
finally 'c' is the top element and 'stack' points to it,
'c' stores the address of the previous element (b)

stack: 0x7fffffffdc60   c.bits: 7fffffffdc40


---------- pop<C>() ----------
'b' is the top element and the next one after it is 'a'

ret: 0x7fffffffdc60
stack: 0x7fffffffdc40   b.bits: 7fffffffdc20

---------- pop<B>() ----------
'a' is the top element and the last one in the stack

ret: 0x7fffffffdc40
stack: 0x7fffffffdc20   a.bits: 0

---------- pop<A>() ----------
the stack is empty

ret: 0x7fffffffdc20
stack: 0x0

解答

1. 当前代码是否符合C++标准?

不符合,核心问题如下:

  • 对象状态非法篡改:直接通过reinterpret_cast修改P类型对象的内存(将其前8字节当作void*存储)违反了C++对象模型规则。示例中的A/B/C包含std::bitset,其内部布局未被标准规定,直接修改内存会破坏对象状态,触发未定义行为。
  • 语法错误:pop函数中throw;未指定异常对象,不符合C++语法要求,必须后跟具体的异常表达式(如throw std::runtime_error("栈下溢");)。
  • 指针转换的未定义行为:static_cast<P*>(stack)仅当stack确实指向P类型对象时合法,若栈中混入非P类型对象则触发UB;reinterpret_cast<void**>(p)要求p指向的内存满足void*的对齐要求,若P类型对齐级别低于void*,会导致未对齐访问,属于UB。
  • 兼容性问题:你遇到的编译器报错,本质是部分编译器对reinterpret_cast结果作为左值的支持不符合标准兼容性,也是代码不合规的表现。

2. reinterpret_cast哪些情况会违反C++标准?

reinterpret_cast是C++中风险最高的转换操作,以下场景会触发未定义行为或违反标准:

  • 跨类型访问内存:将int*转为float*后解引用,或无关类类型指针互转后访问成员,属于未定义行为。
  • 违反对齐要求:转换后的指针指向的内存不满足目标类型的对齐要求(如将char*转为double*但地址未对齐到8字节),解引用会触发UB。
  • 函数指针与非函数指针互转:标准规定这种转换结果未定义,即便转换回原类型也不保证可用性。
  • 篡改对象类型表示:将对象指针转为其他类型指针并修改内容,除非两种类型对象表示完全兼容(如标准布局类型首成员相同),否则属于UB。
  • 指针转整数超出范围:若指针大小大于目标整数类型大小,转换结果未定义。
  • 去除const/volatile后修改:虽语法允许,但通过转换后的指针修改原const对象会触发UB(const_cast才是标准的限定符转换方式,但修改const对象本身依然是UB)。

内容的提问来源于stack exchange,提问作者Alexander S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:55:54