重用内存位置安全吗?

时间:2016-02-11 08:16:16

标签: c++

此问题基于一些移植到C ++的现有C代码。我只是感兴趣它是否安全"。我已经知道我不会这样写的。我知道这里的代码基本上是C而不是C ++,但它是用C ++编译器编译的,我知道标准有时会略有不同。

我有一个分配一些内存的函数。我将退回的void*投射到int*并开始使用它。

稍后我将退回的void*投放到Data*并开始使用它。

这在C ++中是否安全?

示例: -

void* data = malloc(10000);

int* data_i = (int*)data;
*data_i = 123;
printf("%d\n", *data_i);

Data* data_d = (Data*)data;
data_d->value = 456;
printf("%d\n", data_d->value);

我从未读过通过与存储类型不同的类型使用的变量,但担心编译器可能会看到data_idata_d是不同的类型,因此不能合法地互相别名并决定重新排序我的代码,例如在第一个data_d之前将商店放到printf。哪会破坏一切。

然而,这是一直使用的模式。如果您在两次访问之间插入freemalloc,我不相信它会改变任何内容,因为它不会触及受影响的内存本身,并且可以重复使用相同的数据。< / p>

我的代码是否被破坏或者是否正确&#34;?

8 个答案:

答案 0 :(得分:52)

它是“OK”,它就像你编写它一样工作(假设原语和普通旧数据类型(POD))。这很安全。它实际上是一个自定义内存管理器。

一些注意事项:

  • 如果在分配的内存的位置创建了具有非平凡析构函数的对象,请确保它被调用

    obj->~obj();
    
  • 如果要创建对象,请考虑placement new syntax而不是普通模型(也适用于POD)

    Object* obj = new (data) Object();
    
  • 检查nullptr(或NULL),如果malloc失败,则返回NULL

  • 对齐应该不是问题,但在创建内存管理器时始终要注意它并确保对齐是合适的

鉴于您使用的是C ++编译器,除非您希望保持代码的“C”性质,否则您还可以查看全局operator new()

与往常一样,一旦完成,请不要忘记free()(或delete如果使用new

你提到你还没有转换任何代码;但是如果或者当你考虑它时,你可能希望在malloc甚至全局::operator new上使用C ++中的一些惯用功能。

您应该查看智能指针std::unique_ptr<>std::shared_ptr<>,并允许它们处理内存管理问题。

答案 1 :(得分:25)

根据Data的定义,您的代码可能会被破坏。无论如何,它都是糟糕的代码。

如果Data是普通旧数据类型(POD,即基本类型的typedef,POD类型的结构等),和已分配的内存正确对齐类型(*),那么你的代码是定义明确的,这意味着它将&#34; work&#34; (只要你在使用之前初始化 *data_d的每个成员),但这不是一个好习惯。 (见下文。)

如果Data是非POD类型,那么您将遇到麻烦:例如,指针赋值不会调用任何构造函数。 data_d,类型为&#34;指向Data&#34;的指针,实际上是撒谎因为它指向某个东西,但是不是类型Data ,因为没有创建/构造/初始化这种类型。那时未定义的行为就不远了。

在给定内存位置正确构建对象的解决方案称为placement new

Data * data_d = new (data) Data();

这指示编译器在Data 位置构造data对象。这适用于POD和非POD类型。您还需要调用析构函数(data_d->~Data())以确保它在delete内存之前运行。

  

请注意不要混淆分配/释放功能。无论您malloc()需要free() d,new需要delete分配什么,如果new [],您必须delete []。任何其他组合是UB。

无论如何,使用&#34;裸体&#34;在C ++中不鼓励使用内存所有权的指针。你应该

  1. new放在构造函数中,并在类的析构函数中使用相应的delete,使对象成为内存的所有者(包括正确的释放时)该对象超出范围,例如在例外的情况下);或

  2. 使用smart pointer为您有效地执行上述操作。

  3. (*):已知实现定义&#34;扩展&#34;类型,malloc()不考虑其对齐要求。我不确定语言律师是否还会打电话给他们&#34; POD&#34;,实际上。例如,MSVC对malloc()执行8-byte alignment,但将SSE扩展类型__m128定义为16-byte alignment要求。

答案 2 :(得分:16)

围绕严格别名的规则可能非常棘手。

严格别名的一个例子是:

int a = 0;
float* f = reinterpret_cast<float*>(&a);
f = 0.3;
printf("%d", a);

这是严格的别名违规,因为:

  • 变量的生命周期(及其使用)重叠
  • 他们通过两个不同的“镜头”解释同一块记忆

如果您没有同时执行两个,那么您的代码不会违反严格的别名。

在C ++中,对象的生命周期在构造函数结束时开始,在析构函数启动时停止。

对于内置类型(无析构函数)或POD(普通析构函数),规则是,只要内存被覆盖或释放,它们的生命周期就会结束。

注意:这是专门用于支持编写内存管理器的;毕竟malloc是用C语言编写的,而operator new是用C ++编写的,并且明确允许它们汇集内存。

我专门使用镜头而不是类型,因为规则有点困难。

C ++通常使用名义输入:如果两种类型具有不同的名称,则它们是不同的。如果您访问动态类型T的值,就好像它是U一样,那么您就是在违反别名。

此规则有许多例外情况:

  • 基类访问
  • 在POD中
  • ,作为指向第一个属性的指针

最复杂的规则与union有关,其中C ++转移到结构类型:如果你只是在开头访问部分,你可以通过两种不同的类型访问一块内存这段内存中两种类型共享一个共同的初始序列。

  

§9.2/ 18 如果标准布局联合包含两个或多个共享公共初始序列的标准布局结构,并且标准布局联合对象当前包含这些标准之一 - 布局结构,允许检查其中任何一个的共同初始部分。如果相应的成员具有布局兼容类型且两个成员都不是位字段,或者两者都是具有相同宽度的位字段,则一个或多个初始成员的序列,两个标准布局结构共享一个共同的初始序列。

假设:

  • struct A { int a; };
  • struct B: A { char c; double d; };
  • struct C { int a; char c; char* z; };

union X { B b; C c; };内,您可以同时访问x.b.ax.b.cx.c.ax.c.c;但是,如果当前存储的类型不是x.b.d(分别不是x.c.z),则访问B(分别为C)会违反别名。

注意:非正式地,结构类型就像将类型映射到其字段的元组(展平它们)。

注意:char*明确免除此规则,您可以通过char*查看任何内存。

在你的情况下,如果没有Data的定义,我不能说是否可以违反“镜头”规则,但是因为你是:

  • 在通过Data
  • 访问内存之前,使用Data*覆盖内存
  • 之后不通过int*访问

然后你符合生命周期规则,因此就语言而言,没有发生混叠。

答案 3 :(得分:8)

只要记忆一次只用于一件事,它就是安全的。您基本上将分配的数据用作union

如果您想将内存用于类的实例而不仅仅是简单的C风格结构或数据类型,您必须记住将新的放置到&#34;分配&#34 ;对象,因为它实际上会调用对象的构造函数。您在完成对象后必须明确调用的析构函数,您不能delete它。

答案 4 :(得分:6)

只要您只处理“C”类型,这就没问题。但是一旦你使用C ++类,你就会遇到正确的初始化问题。如果我们假设Data例如std::string,那么代码就会非常错误。

编译器无法在调用printf时真正移动商店,因为这是一个可见的副作用。结果必须好像副作用是按照程序规定的顺序产生的。

答案 5 :(得分:5)

实际上,您已经在<script src="http://code.jquery.com/jquery.min.js"></script> <td> <input type="text" maxlength="4" tabindex="1" /> </td> <td> <input type="text" maxlength="4" tabindex="3" /> </td> <td> <input type="text" maxlength="4" tabindex="4"/> </td> <td> <input type="text" maxlength="4" tabindex="2" /> </td> <script type="text/javascript"> jQuery("input").on('input',function () { if(jQuery(this).val().length == jQuery(this).attr('maxlength')) { nextinput = parseInt(jQuery(this).attr('tabindex'))+1; // Here you can take current input value and set where you want jQuery("input[tabindex="+nextinput+"]").focus(); } }); </script> / public Meetings retrieveMeetingDetailsByEmail(string EmailAddress) { using(SqlConnection EMTConnection2 = new SqlConnection()) { string constring = ""; //Initialize connection string here EMTConnection2.ConnectionString = constring; EMTConnection2.Open(); string EMTQuery2 = "Select * from dbo.Meeting where PresiderEmailAddress = @PresiderEmailAddress"; using(SqlCommand EMTCommand2 = new SqlCommand(EMTQuery2, EMTConnection2)) { EMTCommand2.Parameters.AddWithValue("@PresiderEmailAddress", EmailAddress); var meeting = new Meetings(); meeting.PresiderEmailAddress = EmailAddress; using (SqlDataReader dr2 = EMTCommand2.ExecuteReader(); { if (dr2.HasRows) { meeting.Allmeeting = new List<MeetingDetails>(); while (dr2.Read()) { meeting.Allmeeting.add(new MeetingDetails{MeetingId = Convert.ToInt64(dr2["MeetingId"]), MeetingNumber = dr2["MeetingNumber"].ToString(), MeetingName = dr2["MeetingName"].ToString(), MeetingDescription = dr2["MeetingDescription"].ToString(), MeetingDate = Convert.ToDateTime(dr2["MeetingDate"]).ToShortDateString().ToString(), MeetingVenue = dr2["MeetingVenue"].ToString(), TimeStarted = dr2["TimeStarted"].ToString(), TimeEnded = dr2["TimeEnded"].ToString(), CompletionRate = Convert.ToDecimal(dr2["CompletionRate"]), Remarks = dr2["Remarks"].ToString()); } } dr2.Close(); } EMTConnection2.Close(); } } return meeting; } 之上实现了自己的分配器,在这种情况下重用了一个块。那是非常安全的。只要块足够大并且来自保证充分对齐的源(并且malloc确实如此),分配器包装器当然可以重用块。

答案 6 :(得分:5)

只要boost_1_60_0/boost/signal.hpp|17 col 4| warning: #warning "Boost.Signals is no longer being maintained and is now deprecated. Please switch to Boost.Signals2. To disable this warning message, define BOOST_SIGNALS_NO_DEPRECATION_WARNING." [-Wcpp]仍然是POD,这应该没问题。否则你将不得不切换到新的位置。

然而,我会放置一个静态断言,以便在以后的重构期间不会改变

答案 7 :(得分:4)

我没有发现重用内存空间的任何错误。只有我关心的是悬空参考。如你所说,重用内存空间我觉得它对程序没有任何影响 你可以继续你的编程。但它始终优先于free()空格,然后分配给另一个变量。