C ++与Managed中这些指针错误的后果

时间:2009-02-06 01:00:53

标签: java c++ exception pointers managed

我正在将其作为社区维基,以便更好地理解这些错误与其运行时或编译后果之间的语义差异。另外,我用Java编写的代码太长了,我想在C ++中更好地学习指针 - 所以我需要其他人来做。

Edit2:我正在重构这个问题。我试图得出的区别是,在托管代码上,这些错误都是通过异常统一处理的。但是,C ++并不是那么简单 - 我想知道在每种情况下你是否可能来获取错误,段错误,可恢复行为或更糟糕的沉默错误传播。请参阅我的新具体示例(是的,我知道答案总是“完全按照编码”;毕竟我是程序员。我想知道你经常遇到的有趣细节。)< / p>

编辑3:在下文中,“class”代替了一个类的实例。感谢

错误1: 指针值为NULL,又称指针== 0

  • 托管代码:在运行时抛出NullPointerException
  • C ++:?
  • 示例:嗯,嗯,你有一个指向的指针,但它被初始化为0.当你把它发送给一个函数时会发生什么。即。 C ++没有留下任何类的指示;它只是公共“占位符”的串联。

错误2: 指针指向内存中以前的类,其值为NULL或== 0

  • 托管代码:不允许通过内存模型。所有引用的对象都保留在内存中没有特例?
  • C ++:?
  • 示例:您有一个指向的指针,该类已被删除。然后,将指针作为参数传递给函数。显然,发生的问题取决于函数如何处理指向类。我的问题是:STL上是否存在故障安全处理?一个好的专有库?平均开源代码?

错误3:指针指向不属于正确类或子类的类

  • 托管代码:抛出ClassCastException。
  • C ++:[如果错误则更正]编译器尝试通过不允许错误的强制转换来对抗此问题。但是,如果这是在运行时发生的,我会假定未定义的行为。是否有类似类对象的情况,这不会总是爆炸?
  • 示例:您的指针被错误地重新分配,使其值完全等于另一个类。我假设您传递此引用类的函数将盲目地获取它引用的任何实例变量的偏移量。因此,它错误地解释了原始二进制文件。在C ++中无法阻止这种情况?和/或......有没有这种能力被利用的好处?

错误4:指针指向类的中间(未对齐)或未初始化的垃圾

  • 托管代码:内存模型不允许。
  • C ++:相当于案例3?
  • 示例:您实际上经常使用此法律。例如,您可以直接访问STL向量的数组 - 这指向类的中间。然而,似乎同样容易“错过”?是否有一个常见的陷阱,你可能会违背自己的意愿发生这种情况,比如加载的库与你链接的库不同(并且有一种机制可以阻止它吗?)

先感谢所有贡献者。

8 个答案:

答案 0 :(得分:1)

其中大多数都会导致不可预测的行为。引用Steve McConnell的Code Complete第2版“使用指针本质上很复杂,正确使用它们需要您对编译器的内存管理方案有很好的理解”。

答案 1 :(得分:1)

好。在C ++中,取消引用除了案例2之外的任何指针都会产生未定义的行为,所以你不知道会发生什么。但是,对于大多数操作系统,取消引用空指针将导致分段错误。

在比较中使用指针对于空指针来说很好,但对于除了那个和情况2之外的任何其他情况都没有精确定义(未指定)。

案例2是完美定义的。你可以让你的指针指向一个值为0的int。我不明白为什么这样的东西在C#中是非法的。可能我误解了你的情况2

对于案例3,您必须区分指针是否已经指向该错误对象,或者您是否仍在尝试使其指向该错误对象。 C ++ dynamic_cast将检查您指向的对象的类型,如果它不是派生的或者类型与您输入的类型相同,那么它将为您提供一个空指针。但是还有其他的演员没有做那个检查,并会给你留下无效的指针。

答案 2 :(得分:1)

  1. 指针值为NULL,又称指针== 0 未定义的行为。允许编译器做任何想做的事情,包括每次不同的事情。在大多数基于unix的系统中,这将导致分段错误。
  2. 访问已删除的指针这是未定义的行为。在某些情况下,根据内存分配和使用的确切模式,您可以使用已删除的指针,就好像它没有被删除一样,如果内存没有被重用于其他内容。这可能导致很难追踪错误。如果再次删除指针,则可能会破坏内存分配系统,导致完全不相关的新闻/删除崩溃
  3. 指针指向不属于正确类或子类的类 C ++不执行运行时类型检查。它将尝试将内存位置解释为指针的类型。如果尚未在该位置创建正确类型的对象,则它是未定义的行为,并且可能发生任何行为(包括看似正常工作)。
  4. 指针指向类的中间(未对齐)或未初始化的垃圾与上述相同,未定义的行为。
  5. 总之,你不能依赖任何有价值的东西。设计代码非常重要,这样才不会发生。编译器可以帮助它,因此在尝试欺骗它时要非常小心(例如,强制转换)。编译器最终会报复。

答案 3 :(得分:0)

#1应该抛出一个段错误。

#2,#3和#4可能有效,具体取决于方法尝试的方法。请记住,在C ++中,类代码只存储一次(并且与实例数据分开,这是对象指针引用的),因此可以在随机的内存块上调用类方法。例如,以下打印“-1”(使用g ++ 4测试):

#include <iostream>

class Foo
{
public:
    int x;
    void foo()
    {
        std::cout << x << std::endl;
    }
};

int main(void)
{
    void* mem = malloc(1024);
    memset(mem, 0xff, 1024);
    Foo* myFoo = (Foo*)mem;
    myFoo->foo();
    return 0;
}

答案 4 :(得分:0)

在Windows错误1下,当您尝试访问您没有读取权限的虚拟内存页面时,将导致针对访问冲突引发结构化(win32)异常。 Unix派生的操作系统有一个类似的机制,虽然术语不同。

这是明确定义的(如果通常是不合适的!)行为,并且可以被结构化异常处理程序捕获。通常,托管运行时将依赖于底层操作系统引发此异常,然后处理它并将其转换为托管异常。这比在跟踪它之前检查每个指针访问空间要高效得多。

答案 5 :(得分:0)

这是我修改过的错误版本:

错误1:空指针/引用

  • 托管代码:如果是引用则抛出NullReferenceException;如果是指针则抛出AccessViolationException(是!托管代码中存在指针!)
  • 原生代码:在Windows上,这会导致“访问冲突”(通常称为AV)。在Unix上,这将被称为“seg fault”。在Windows上,理论上可以使用异常处理来捕获它

错误2:指向已释放的对象的指针

  • 托管代码:一般未定义, 但很有可能 AccessViolationException。 (注意 这指的是实际的指针使用情况, 没有托管引用,这将是 永远有效)
  • 原生代码: 通常是未定义的,但可能是一个 访问违规。

错误3:

  • 托管代码:引发异常
  • 本地 代码:取决于演员表的类型, 如果是a将是编译器错误 静态强制转换或未定义的结果 如果重新解释演员。

错误4:

  • 托管代码:未定义
  • 原生代码:未定义

答案 6 :(得分:0)

我只想添加这一点信息。指针会做任何你告诉他们要做的事情。如果程序可以访问所述内核,则包括覆盖内核。

以第3点为例,这是许多内核攻击中使用的技术。找出内核所在的位置,并使用指针来更改信息。我绝不建议有人试试这个,我不会宽恕使用Rootkit或任何其他恶意软件。

答案 7 :(得分:0)

如果您真的想了解指针,因为您想更好地了解您的计算机,请专注于C或汇编。实际上,有一些用C编写的很棒的小型C编译器,将它们分开并将它们重新组合在一起。

C ++降级为C(我的意思是它可以编译C文件),但是在C ++中还有很多要处理的东西,而对于C,你可以只考虑指针的基础知识。

我还强烈建议您使用汇编语言编译C程序并跟踪(单步调试)。如果你真的想要理解底层系统,理解堆栈帧和调用期间发生的事情是非常关键的。

学习这些东西的其他方法:

  • 在编译器构造中审核一个类。
  • 使用PIC控制器构建一些有趣的东西 - 机器人或计算器。