当C++编译器为引用和指针生成非常相似的汇编代码时,为什么使用引用比使用指针更受欢迎(并被认为更安全)?
我看到了C++中指针变量和引用变量之间的区别,讨论了它们之间的区别。
编辑-1:
我正在查看由g++为此小程序生成的汇编代码:
int main(int argc, char* argv[])
{
int a;
int &ra = a;
int *pa = &a;
}
当C++编译器为引用和指针生成非常相似的汇编代码时,为什么使用引用比使用指针更受欢迎(并被认为更安全)?
我看到了C++中指针变量和引用变量之间的区别,讨论了它们之间的区别。
编辑-1:
我正在查看由g++为此小程序生成的汇编代码:
int main(int argc, char* argv[])
{
int a;
int &ra = a;
int *pa = &a;
}
许多人认为使用引用更加安全,因为许多人“听说”过它比指针更加安全,并将这一信息传递给了其他人,现在这些人也“听说”过引用更加安全。
但是,任何理解引用和指针的人都不会告诉你它们比指针更加安全,它们具有相同的缺陷和可能失效的潜力。
例如:
#include <vector>
int main(void)
{
std::vector<int> v;
v.resize(1);
int& r = v[0];
r = 5; // ok, reference is valid
v.resize(1000);
r = 6; // BOOM!;
return 0;
}
编辑:由于一些人对于引用是对象的别名还是绑定到内存位置存在困惑,这里有一段标准草案(第3225版,章节[basic.life]
),明确说明了引用是绑定到存储空间的,并且可以超过创建引用时所存在的对象的生命周期:
如果在一个对象的生命周期结束后,在该对象占用的存储空间被重用或释放之前,在该原始对象所占用的存储位置上创建了一个新对象,则指向原始对象的指针、引用原始对象的引用或原始对象的名称将自动引用到新对象,并且一旦新对象的生命周期开始,就可以用来操作新对象,如果:
- 新对象的存储正好覆盖了原始对象占用的存储位置,并且
- 新对象与原始对象具有相同的类型(忽略顶层cv限定符),并且
- 原始对象的类型没有const限定符,并且,如果是类类型,则不包含任何类型为const限定符或引用类型的非静态数据成员,并且
- 原始对象是类型T的最派生对象,而新对象是类型T的最派生对象(即它们不是基类子对象)。
if (ptr)
称为“容错处理”就是荒谬的。但是,确实有其他人注意到编译器可以使引用变得安全,它们必须通过正确的代码使其变得安全——就像指针一样。 - Ben Voigtint *const p
也具有该特性。关于空值,[[gnu::nonnull]] int *p
也具有该特性。顺便说一下,如果您将(可能为NULL的)指针分配给非空指针或引用,则编译器不会发出警告。因此,最终,引用与 [[gnu::nonnull]] int *const p
一样不安全(并且在我看来更加丑陋)。 - alx - recommends codidact_Nonnull
,否则使用指针可以获得比使用引用更多的警告。引用是伪装成[[gnu::nonnull]] * const
指针的东西。 - alx - recommends codidact引用是其他变量的别名,按照定义不能为NULL
,从而提供了内在的安全层。
void f(int& i) { i = 0; }
无论何时都是如此,不管发生什么情况。如果我遇到了错误,我永远不需要考虑这个函数是导致错误的原因。相反,这个函数是可能会出错的: void f(int* i) { *i = 0; }
,并且当传递一个空指针给这个函数时,我必须在面对错误时要考虑这种情况。引用的作用是使我的函数自包含,并将正确处理任何可能拥有的指针的责任放在函数的“用户”身上。 - GManNickG引用始终从现有对象初始化,因此它永远不会为NULL,而指针变量允许为NULL。
编辑:感谢所有回复。是的,引用确实可以指向垃圾,我忘记了悬挂引用。
这样做相对安全一些,但并不完全相同。请注意,您仍然存在“悬空引用”的问题,就像“悬空指针”一样。例如,从作用域对象返回引用会产生未定义的行为,与指针完全相同:
int& f() { int x = 2; return x; }
int& null_ref = *((int*)0); // Dereferencing a null pointer is undefined in C++
// The variable null_ref has an undefined state.
null_ref
的内容是未定义的。 - Alexandre C.null_ref
的值是未知的,当然,整个程序的状态也是未知的。 - GManNickG因为引用必须始终初始化,且必须引用现有对象,所以相比于未初始化/悬空指针,悬空引用的出现更难(但并非不可能)。此外,操作引用更容易,因为您无需担心获取地址和解除引用。
但是,仅凭引用本身并不能使您的程序100%安全,请考虑以下情况:
int *p = NULL;
int &r = *p;
r = 10; /* bad things will happen here */
int &foo() {
int i;
return i;
}
...
int &r = foo();
r = 10; /* again, bad things will happen here */
好的,你指出的答案回答了这个问题。从“安全”的角度来看,我认为基本上很难编写像这样的代码:
int* i;
// ...
cout << *i << endl; // segfault
由于引用始终被初始化,因此
MyObject* po = new MyObject(foo);
// ...
delete po;
// ...
po->doSomething(); // segfault
但正如你提到的问题中所说,不仅因为它们更安全,引用才被使用...
我的个人看法
<type>&
。那么,每当您想要将对象的引用传递给函数时,您必须执行C中所做的操作,将参数作为&<myobject>
传递,并将其作为指针参数<type>* p
在函数中接收。然后,您必须使自己不要像p++
这样做。&
更加简洁。而且它可以应用于除参数之外的其他变量。)