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

通过指针给std::string赋值时memcpy.asm错误的解决方法

自定义Stack模板处理std::string时触发访问违例的原因与解决思路

问题描述

我实现了一个固定大小的Stack模板结构体,处理基本类型和自定义结构体时均正常,但使用std::string时抛出access violation reading location异常,错误发生在memcpy.asm中,MSVS2019显示错误指向赋值语句的下一行有效代码。计算得sizeof(std::string)为28字节,64字节的Stack理论上足够容纳它加额外的大小标识字节,求问题原因和解决思路。

原代码

// Stack结构体代码
#include<iostream>
#include<Windows.h>
template<uint32_t S>
struct Stack {
    size_t size;
    byte base[S];
    byte* top;
    //
    Stack()
    {
        top = (byte*)base;
        size = 0;
    }
    ~Stack(){}
    //
    template<class T>
    void alloc(T*& rt)
    {
        if (sizeof(T) > MAXBYTE) return;
        rt = (T*)top;
        top += sizeof(T);
        size += sizeof(T) + 1;
        *top = sizeof(T);
        top++;
    }
    void alloc(void*& rt, size_t arg)
    {
        if (arg > MAXBYTE) return;
        rt = top;
        top += arg;
        size += arg + 1;
        *top = arg;
        top++;
    }
    template<class T>
    void* push(T arg)
    {
        if (arg > MAXBYTE) return 0;
        T* rt;
        alloc<T>(rt);
        *rt = arg;
        return rt;
    }
    template<class T>
    T&& pop()
    {
        if (top == base) return 0;
        top--;
        size -= *top;
        top -= *top;
        return (T&&)*(T*)top;
    }
    void pop()
    {
        if (top == base) return;
        top--;
        size -= *top;
        top -= *top;
    }
};

// 测试代码
int main()
{
    Stack<64> t;
    std::string* a;
    t.alloc(a);
    *a = "TEST";
    //
    std::cout << "press enter to exit...";
    std::cin.get();
    return 0;
}

问题原因

  1. 内存对齐不满足:std::string作为非平凡类型,在64位系统下通常要求8字节对齐。Stack直接将top指针强制转换为T*,但top的地址可能不满足std::string的对齐要求,赋值操作时触发内存访问错误。
  2. 未构造对象直接赋值:alloc函数仅分配了内存空间,未调用std::string的构造函数。std::string内部维护着指向堆内存的指针等状态,未初始化的对象直接赋值会导致未定义行为,比如访问野指针。
  3. 边界检查不严谨:alloc仅检查单个元素大小是否超过MAXBYTE,未验证Stack剩余空间是否足够容纳元素加标识字节(虽然本次理论空间足够,但逻辑存在漏洞)。
  4. pop函数逻辑错误:返回T&&的实现中,返回的是已释放内存位置的引用,属于悬垂引用,后续访问会触发未定义行为。

解决思路

  1. 保证内存对齐:使用std::align(需包含<memory>头)调整top指针,确保分配的地址满足T的对齐要求。
  2. 正确构造与析构对象:
    • 分配内存后,用定位new(new (rt) T())构造对象;
    • 弹出对象时,必须调用对象的析构函数,避免资源泄漏和未定义行为。
  3. 完善边界检查:计算Stack剩余空间,判断是否足够容纳当前元素加标识字节,不足则拒绝分配。
  4. 修正pop函数逻辑:避免返回悬垂引用,改为通过输出参数返回弹出的对象,确保操作的安全性。

修正后的示例代码

#include<iostream>
#include<Windows.h>
#include<string>
#include<memory> // 用于std::align

template<uint32_t S>
struct Stack {
    size_t size;
    byte base[S];
    byte* top;

    Stack() : top(reinterpret_cast<byte*>(base)), size(0) {}
    ~Stack() {
        // 析构所有剩余对象
        while (top != base) {
            pop();
        }
    }

    template<class T>
    bool alloc(T*& rt) {
        const size_t elem_size = sizeof(T);
        const size_t required = elem_size + 1;
        const size_t remaining = S - (top - base);
        
        // 检查元素大小和剩余空间
        if (elem_size > MAXBYTE || remaining < required) {
            return false;
        }
        // 调整指针以满足对齐要求
        std::align(alignof(T), elem_size, top, remaining);
        rt = reinterpret_cast<T*>(top);
        // 定位new构造对象
        new (rt) T();
        top += elem_size;
        size += required;
        *top = static_cast<byte>(elem_size);
        top++;
        return true;
    }

    template<class T>
    bool push(const T& arg) {
        T* rt;
        if (!alloc(rt)) {
            return false;
        }
        *rt = arg;
        return true;
    }

    template<class T>
    bool pop(T& out) {
        if (top == base) {
            return false;
        }
        top--;
        size_t elem_size = *top;
        // 校验类型大小匹配
        if (elem_size != sizeof(T)) {
            top++;
            return false;
        }
        top -= elem_size;
        T* elem = reinterpret_cast<T*>(top);
        out = std::move(*elem);
        elem->~T(); // 析构对象
        size -= elem_size + 1;
        return true;
    }

    void pop() {
        if (top == base) {
            return;
        }
        top--;
        size_t elem_size = *top;
        top -= elem_size;
        // 通用pop无法确定类型,建议优先使用带输出参数的pop版本
        size -= elem_size + 1;
    }
};

// 测试代码
int main()
{
    Stack<64> t;
    std::string* a;
    if (t.alloc(a)) {
        *a = "TEST";
        std::cout << "Stored string: " << *a << std::endl;
    }

    std::string out_str;
    if (t.pop(out_str)) {
        std::cout << "Popped string: " << out_str << std::endl;
    }

    std::cout << "press enter to exit...";
    std::cin.get();
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:24:10