【问题标题】:const char* getting modified after assigning to char*const char* 在分配给 char* 后被修改
【发布时间】:2014-08-23 09:03:07
【问题描述】:
int FunctionName(const char *pValueName, const char *pValueData, long iMaxValueSize)
{
  char *pDataToStore = const_cast<char *>(pValueData);
  int iActualSiz = ProcessData(pDataToStore, iMaxValueSize);
...
...
}

在上面的代码中,sn-p ProcessData() 函数修改了作为参数接收的 char*。现在,即使将 pValueData 分配给 pDataToStore,在执行 ProcessData() 之后,pValueData 的值也与 pDataToStore 相同。

我的目标是保持作为 const char* 传递的 pValueData 的完整值

【问题讨论】:

  • 那就不要抛弃const
  • 你的函数错了,界面也错了,向函数的用户坦白谎言
  • const 修饰符可以帮助你,让你的编译器避免你犯错误。如果你把它扔掉,那么它背后的内存就可以被修改。
  • 您是否希望const_cast 复制该数组?如果你想要一个副本,你需要自己复制它。
  • 自己复制 - 你的意思是说传递不同的变量副本作为参数。

标签: c++


【解决方案1】:

我的目标是保持被传递为的 pValueData 的完整值 常量字符*

那是不可能的。通过const 传递意味着它不能被修改,除非它最初不是常量。

例子:

char *ptr1 = new char[100]; // not const
char *ptr2 = new char[100]; // not const
int i = FunctionName(ptr1, ptr2, 123);

在这种情况下,您可以在技术上保留const_cast。但是为了什么?只需将您的函数参数更改为采用char *

int FunctionName(char *pValueName, char *pValueData, long iMaxValueSize)
{
  int iActualSiz = ProcessData(pValueData, iMaxValueSize);
  // ...
}

但是,您很可能希望能够传递常量字符串。例如字符串文字:

int i = FunctionName("name", "data", 123);

字符串文字是不可修改的,因此需要您的函数采用char const *。稍后尝试修改它们会导致未定义的行为。


如您所见,错误出在一般架构和代码逻辑中。 您想修改某些内容,同时又不想允许修改它。

问题是:当ProcessData 完成后,你的pDataToStore 会发生什么? FunctionName 的调用者需要注意修改吗?还是只是FunctionName的内部业务?

如果只是FunctionName的内部业务,那么你可以保持其签名不变,让ProcessData修改一份传递过来的数据。这是一个简化的(不是异常安全的,没有错误检查)示例:

int FunctionName(const char *pValueName, const char *pValueData, long iMaxValueSize)
{
   char *copy = new char[strlen(pValueData) + 1];
   strcpy(copy, pValueData):
   int iActualSiz = ProcessData(copy, iMaxValueSize);

   // ...

   delete[] copy;
}

好消息是您现在可以通过隐藏所有低级指针业务来大规模改进FunctionName 的界面。事实上,既然 C++ 标准类可以为您完成所有工作,为什么还要使用这么多指针呢?

int FunctionName(std::string const &valueName, std::string const &valueData, long maxValueSize)
{
   std::vector<char> copy(valueData.begin(), valueData.end());
   int actualSize = ProcessData(&copy[0], maxValueSize);

   // ...
   // no more delete[] needed here
}

std::vector&lt;char&gt; 自动分配足够的内存来保存valueData 的副本,并执行复制。它会在不再需要内存时完全自动释放内存,即使抛出异常也是如此。并且&amp;copy[0](在 C++11 中可以写成copy.data())保证产生一个指向内部使用数据的指针,以便低级 C 函数可以修改向量的元素。

(我也借此机会删除了微软风格的匈牙利符号。这是 90 年代的一个失败的实验,你甚至错误地使用了它,假设前面的 i 应该是表示int。)


底线真的是:

如果您的代码中需要 const_castanywhere 以使其编译,那么在其他地方至少有一个 constmissing 或一个太多const_cast 总是可以弥补另一段代码中的错误。它始终是一种解决方法,而不是预先设计的解决方案。

【讨论】:

  • +1 太好了!与我的答案相同的结论,但我真的很喜欢你的方法,只需将签名更改为字符串。但是有一个小问题,因为我们不确定 char* 数据是否真的是一个以空字符结尾的字符串。
  • 很好的详细答案,我在底线会说不同:const_cast 应该只在使用来自正确编写的代码的遗留代码时使用。
  • @SergeBallesta:但这不就是我自己的底线所说的吗? :) 也许应该区分“遗留代码”(按照编写时的标准可能已经很棒)和“糟糕的代码”(按照创建时的标准来说已经很糟糕了)。
  • @Christophe:你是对的。如果字符串不是以空值结尾的,那么大小信息必须来自其他地方。也许这就是long 参数的含义?或者尺寸信息来自任何地方完全不同的地方。 ProcessData 可以断言在编译时已知的大小。或者大小可以是全局或静态变量。我们真的无法通过这段简单的代码知道;所以我假设以空字符结尾的字符串。
  • @Christophe:向量解决方案是 C++ 的绝对赢家。感谢您的支持。
【解决方案2】:

我已经通过创建堆内存解决了这个问题。

char *pDataToStore = new char[iMaxValueSize];
memcpy(pDataToStore, pValueData, iMaxValueSize*sizeof(char));
int iActualSiz = ProcessData(pDataToStore, iMaxValueSize);
...
....
delete []pDataToStore;

【讨论】:

  • memcpy 的用法非常糟糕(无论如何它在 C++ 中很少有用)。使用std::copystd::vector&lt;char&gt;;看我的回答。
  • iMaxValueSize 定义为 long,而 new[]memcpy() 只能处理 size_t。 @ChristianHackl 您对风格的看法是正确的,但是从性能表现的角度来看,这是一种有效的方法。
  • @Christophe:不正确。请参阅stackoverflow.com/questions/4707012/c-memcpy-vs-stdcopystackoverflow.com/questions/7285952/… 以及有关 memcpy 与 std::copy 的许多相关问题。
  • @Christophe size_t 可以容纳单个分配的最大字节数
  • @Christophe 添加支票if ( iMaxValueSize &lt; 0 || iMaxValueSize &gt; SIZE_MAX ) /* abort */
【解决方案3】:

您必须区分 const 限定类型const 限定对象

7.1.6.1 节中的标准状态:cv-qualifiers:(cv = const or volatile)

指向 cv 限定类型的指针或引用不需要实际指向 或引用一个 cv 限定的对象,但它被当作是这样处理;一种 const 限定的访问路径不能用于修改对象,即使 引用的对象是非常量对象,可以修改 通过其他一些访问路径。

如果您的指针指向一个非 const 对象,则丢弃将使您能够修改该对象,但正如有人所说,您是在对函数的用户撒谎。

如果您的指针指向一个真正的 const 对象(即在 const 受保护的内存中),编译器将编译您的代码,但您可能会遇到分段错误,这是未定义行为的典型特征。

这里是一个例子,使用 “普通字符串文字 (...) 的类型为“n const char 数组”,其中 n 是字符串的大小 (...)”(参见标准,第 2.14.5 节):

char *my_realconst = "This is a real constant string";    // pointer does not claim that it points to const object 

(*my_realconst)++;  // Try to increment the first letter, will compile but will not run properly !! 

因此,如果您的函数 ProcessData() 是仅读取数据但忘记在参数列表中提及 const 的遗留代码,那么您的弃用代码将起作用。但是,如果您的函数正在更改数据,它可能会起作用,也可能会失败,这取决于指向的数据是如何创建的!

因此,如果您不能 100% 确定效果会怎样,请尽量避免放弃 const !更好地克隆你的对象,创建一个临时对象并复制内容。

【讨论】:

    【解决方案4】:

    我建议你一个小模板来轻松处理这些问题:

    template <typename T>
    class Buffer { 
        size_t sz;      // size
        T* addr;        // pointed
    public: 
        Buffer(const T*source, size_t l) : sz(l), addr(new T[l]) { std::copy(source, source + l, addr);  }  // allocate and copy
        ~Buffer() { delete[]addr; }   // destroy memory 
        operator T* () { return addr; }  // convert to pointer
    };
    

    您几乎可以按原样使用现有代码:

    Buffer<char> pDataToStore(pValueData, iMaxValueSize);  // create the automatic buffer
    int iActualSiz = ProcessData(pDataToStore, iMaxValueSize);  // automatic use of pointer to buffer
    cout << "modified copy: " << pDataToStore << endl;
    cout << "original:      " << pValueData << endl;
    

    一旦 pDataToStore 不再在范围内,缓冲区将被自动释放。

    如果您对wchar_t 缓冲区或其他任何东西有类似的问题,它也会起作用。

    关于抛弃 const 的坏处的解释,请看我的另一个回答

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-17
      • 2023-04-02
      • 1970-01-01
      • 1970-01-01
      • 2013-10-22
      • 2016-01-20
      相关资源
      最近更新 更多