声明堆栈变量而不指定名称并获取指针

Posted

技术标签:

【中文标题】声明堆栈变量而不指定名称并获取指针【英文标题】:Declare stack variable without specifying the name and get the pointer 【发布时间】:2013-06-05 01:43:10 【问题描述】:

众所周知,用new定义堆变量会得到指针而不指定名称:

Var *p = new Var("name", 1);

但我必须稍后在程序中用delete p 清除p 指向的变量。

我想声明一个堆栈变量,以便在函数退出后自动清除它,但我只想获取指针,以及以下内容:

Var v("name", 1);
Var *p = &v;

相当乏味,并且永远不会引用说明符v

我可以声明一个堆栈类实例并在不指定其名称的情况下获取其指针吗?

【问题讨论】:

Var *p = Var("name", 1); 不起作用? 嗯,你真的需要通过指针来访问对象吗? 我会坚持下去——我认为答案是“不,你不能那样做;也就是说,你不能在堆栈上定义一个匿名变量”。 只在需要指针版本的位置使用“&v”有什么问题? “获取指针而不指定名称”是什么意思? 【参考方案1】:

这里隐藏了两个问题。第一个是:

Var *p = new Var("name", 1);

但是我必须稍后用 delete p 清除 p 指向的变量 在程序中打开。

我想声明一个堆栈变量,以便自动清除它 函数退出后

所以在这里,您要问的是如何分配内存,而无需事后显式清理它。解决方法是使用std::unique_ptr:

std::unique_ptr<Var> p(new Var("name", 1));

瞧! unique_ptr 会自动清理自己,与原始指针相比,它几乎没有开销,并且它重载了 * 和 -> 运算符,因此您可以像使用原始指针一样使用它。如果您想了解更多信息,请搜索“C++11 智能指针”。

第二个问题是:

我只想获取指针,以及以下内容:

Var v("name", 1);
Var *p = &v;

相当繁琐,说明符v永远不会被引用。

这里的重点是Var *p = &amp;v 是完全没有必要的。如果你有需要指针的函数,可以当场使用&amp;v

void SomeFunc(const Var* p);
// ...
Var v("name", 1);
SomeFunc(&v);

在将 &v 传递给需要指针的函数之前,无需将其放入单独的变量中。

如果函数将引用指向指针(或指向指针的指针),则例外:

void SomeFunc2(Var*& p);
void SomeFunc3(Var** p);

这些类型的函数在现代 C++ 中很少见,当您看到它们时,您应该非常仔细地阅读该函数的文档。通常,这些函数会分配内存,您必须使用其他函数显式释放它。

【讨论】:

一个吹毛求疵:您可能应该澄清unique_ptr 指向的对象在此处的堆栈中不是;它在堆中。诚然,OP 对堆的抱怨仅仅是原始指针不进行自动清理,这当然可以通过unique_ptr 解决,但仍然值得知道堆内存正在这里分配和释放。此外,它不能“像原始指针一样”使用,因为为了将它传递给实际采用原始指针的函数(正如您所指出的,这可能是 OP 的用途case here),您必须使用get() (就我个人而言,我是not sold on get(),尽管从对该问题的投票来看,似乎大多数人都不同意我的观点。) 是的,正如我在答案中提到的,实际上有两个问题被问到:如何分配内存而不必显式清理它?以及如何将堆栈变量传递给需要指针的函数? get() 而言,我认为对其使用持批评态度是公平的。只有在与遗留代码交互时才真正需要。如果您可以完全控制所有代码,通常可以完全避免它。 对,我并不是说您的回答没有回答(预期的)问题,我只是说没有经验的 C++ 用户可能不清楚堆栈/堆的区别,所以这可能是值得一提,尤其是考虑到问题的标题。【参考方案2】:

没有办法通过在堆栈上分配来做到这一点。但是,您可以将std::make_shared 用于堆:

#include <memory>

std::shared_ptr<Var> p = std::make_shared<Var>();

【讨论】:

这比 OP 已经拥有的要优雅得多,而且在打字方面绝对不那么乏味 ;) 这分配在空闲存储(堆)而不是堆栈上。 @0x499602D2 嗯...我只是在寻找new 的对应物来声明堆栈变量。 默认是prefer unique_ptr to shared_ptr。 这在堆而不是堆栈上分配。即使这是可以容忍的,这里也绝对不需要共享所有权,std::unique_ptr 将是合适的解决方案(提示:这适用于大多数情况下,您想向std::shared_ptrs 发送垃圾邮件)。【参考方案3】:

以更加混乱为代价/风险,您可以避免在问题 ala 的代码中重复 type

Var v("name", 1), *p = &v;

您也可以使用alloca,它由大多数系统提供并返回一个指向堆栈分配内存的指针,但是您必须通过一个单独的痛苦步骤将new 一个对象放置到该内存中并且做你自己的对象销毁。 alloca 需要在函数内部调用,因此它是在其上创建对象的函数堆栈,而不是在准备函数参数期间(因为变量的内存可能嵌入在编译器用于准备函数参数的堆栈区域中) ,这使得包装到一些易于重用的设施中变得很棘手。您可以使用宏,但它们是邪恶的(请参阅 Marshall Cline 的 C++ FAQ 以获得对此的解释)。总的来说 - 不值得痛苦....

无论如何,我建议坚持使用您问题中的代码,不要过度思考:使用 &amp;v 几次往往会更容易,如果不是,如果有不必要的标识符,通常没什么大不了的基于堆栈的变量。

【讨论】:

天哪,它坏了。 alloca 一开始就足够危险,没有人从构造函数内部返回指向它给你的堆栈位的指针。废话! @kfsone:这真是令人震惊,即使我自己也这么说 ;-)【参考方案4】:

我认为没有办法克服它而不需要一些开销(比如 shared_ptr)。所以写它的最短方法是:

Var v("name", 1), *p = &v;

【讨论】:

【参考方案5】:

是的,可以将地址返回给临时(即堆栈)对象并将其分配给指针。但是,编译器实际上可能会在当前范围结束之前丢弃对象(即导致内存中的该部分被覆盖)。 (澄清:这意味着永远不要这样做。。)请参阅下面的 cmets 中关于在不同操作系统上的不同版本的 GCC 中观察到的行为的讨论。 (我不知道版本 4.5.3 仅给出警告而不是错误这一事实是否表明这将始终是“安全的”,因为如果您编译,指针将在当前范围内的任何地方都有效使用特定版本的 GCC,但我不会指望它。)

这是我使用的代码(根据 Jonathan Leffler 的建议进行了修改):

#include <stdio.h>

class Class 
public:
    int a;
    int b;
    Class(int va, int vb)a = va; b = vb;
;

int main() 
    Class *p = &Class(1, 2);
    Class *q = &Class(3, 4);
    printf("%p: %d,%d\n", (void *)p, p->a, p->b);
    printf("%p: %d,%d\n", (void *)q, q->a, q->b);

使用 GCC 4.5.3 编译并运行(在 Windows 7 SP1 上)时,会打印以下代码:

0x28ac28: 1,2
0x28ac30: 3,4

当使用 GCC 4.7.1 编译并运行(在 Mac OS X 10.8.3 上)时,它会打印:

0x7fff51cd04c0: 0,0
0x7fff51cd04d0: 1372390648,32767

在任何情况下,我都不确定您为什么不只是正常声明变量并在需要“类似指针”的任何地方使用&amp;v(例如,在需要指针作为参数的函数中) .

【讨论】:

当我在 ***.cpp 文件中编译您的代码时,GCC 4.7.1(g++ -Wall -Wextra 表示:***.cpp: In function ‘int main()’:***.cpp:11:26: error: taking address of temporary [-fpermissive]。请注意,这是 4.7.1 的错误(其中你使用的版本吗?)我认为它试图告诉你临时的持续时间是语句的结尾,所以在语句完成后指针指向任何东西。为了让它编译,我不得不添加@ 987654329@. 当我运行你的代码的这个小变化时:int main() Class *p = &amp;Class(1, 2); Class *q = &amp;Class(3, 4); printf("%p: %d,%d\n", (void *)p, p-&gt;a, p-&gt;b); printf("%p: %d,%d\n", (void *)q, q-&gt;a, q-&gt;b); 我得到垃圾输出。没有%p 打印,(令我惊讶的是)代码打印1,23,4,但有了它,我得到:0x7fff51cd04c0: 0,00x7fff51cd04d0: 1372390648,32767。 (Mac OS X 10.8.3 上的 GCC/G++ 4.7.1) 这是未定义行为的问题;它有时似乎可以按您的预期工作......直到您将代码移动到另一台机器或同一台机器上的不同编译器。根据 Cygwin 安装程序,在 v4.8.0 有一个 cygwin64-gcc,在 v4.7.2 有一个 gcc4(尽管它默认为 4.5.3 版本),在一个古老的 v3.4.4 有一个普通的 gcc .也许升级是为了……或者如果你曾经依赖过这种行为,也许不是。 (注意:无论是否使用 -Wall -Wextra 选项,在 4.7.1 中都是错误;使用 -fpermissive 时不再是错误。) @WagnerPatriota:不;它不是最好的解决方案,因为它依赖于未定义的行为。依赖未定义的行为是危险的——非常危险。这对你的理智很危险;这对您的程序及其数据很危险。 @WagnerPatriota:这是非常严重的错误 - 不仅仅是在某些奇怪的系统上可能实际上表现出未定义的行为,而且可以预期在所有现代主流机器上可靠地工作(因为很多 C++ 未定义的行为是)-相反,这是“如果它对任何人都有效,那已经是侥幸,只需更改优化器级别或在函数的其他地方添加更多变量,然后看着它崩溃和燃烧”。

以上是关于声明堆栈变量而不指定名称并获取指针的主要内容,如果未能解决你的问题,请参考以下文章

声明指针或堆栈变量

在 x86 上使用 GDB 而不调试符号?

如何获取对象的完整类型名称

获取 GDB 便利变量中保存的值的符号信息

c语言如何将两个变量关联起来

c语言fgetc()函数(从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动)