【问题标题】:How are function pointers type unsafe函数指针类型如何不安全
【发布时间】:2012-04-19 22:36:54
【问题描述】:

首先,类型安全意味着编译器在处理不当时可以立即捕获的任何内容。

现在,我听说函数指针不是类型安全的,但是每当我尝试错误地使用它们时,编译器都会为我报告错误。那么,它的类型如何不安全?

例如这是一个函数原型,它接受一个函数指针

void SortElements(void* MyArray, unsigned int iNumofElems,size_t size, int(*compare_funct)(void* First,void* SecondElem))

我已经定义了几个函数来传递给它:

int MySortAsc(void* First, void* Second);
void MyFunct2();
void MyFunct3(void* First);

代码仅编译用于:

SortElements(MyArray, 10, sizeof(DataType), &MySortAsc); //Compiles
SortElements(MyArray, 10, sizeof(DataType), &MyFunct2);  //Fails

知道如何在这里误用函数指针吗?

是不是因为这个:

void (*functionPointer)();
...
int integer = 0xFFFFFFFF;     
functionPointer = (void(*)())integer;      
functionPointer();

回答: 我看到的是 C++ 中的函数指针是类型安全的。当然,它们可以通过不正确地转换来以不安全的方式使用,但这并不使它们成为被称为不安全类型的理由。 .NET 委托也是强类型的,在我看来两者都是类型安全的。

【问题讨论】:

  • 您在哪里看到它被描述为不安全?查看源代码将有助于理解作者的意图。
  • “类型安全”意味着一段代码的正确性仅来自其静态类型,而不依赖于变量的特定。例如,printf 不是类型安全的,因为它的正确性取决于格式字符串的 及其参数类型。
  • 为什么说 .NET 委托与普通函数指针相比​​是类型安全的函数指针?
  • @KerrekSB:这里不是这样。 Printf 是不安全的,因为省略号是不维护类型的全部内容(即调用者和被调用者必须就如何解释类​​型达成一致。从这个角度来看,函数指针与函数一样安全,即使如果在这种特殊情况下由于其他原因不安全:使用 void* 会破坏类型安全。
  • @DavidRodríguez-dribeas 我总是可以在 C++ 中进行强制转换并破坏类型安全,如我在上面的示例中所示,如果我调用 functionPointer() 程序将崩溃。据我所知,在.NET 代表中你不能这样做。从这个意义上说,这是否使函数指针不安全?

标签: c++


【解决方案1】:

那么,它的类型如何不安全?

void SortElements(void* MyArray,              // what type is pointed here?
                  unsigned int N,             // Are there really N elements?
                  size_t size,                // Is the size correct?
                  int(*cmp)(void*,void*));    // Is this the correct function?

您提供的代码类型不安全,不是因为函数指针,而是因为在SortElements 签名和函数指针签名中都使用了void*

这是不安全的原因是因为调用者有传递正确参数的全部责任,而编译器不能确保指针MyArray指向一个连续的内存区域,其中包含iNumofElems,每个内存区域都有界面中提供size。如果程序员犯了错误,编译器将无法提供帮助,如果维护者修改了存储在数组中的类型(大小更改)或元素数量,编译器将无法检测到并告诉您您需要将呼叫更新为SortElements。最后,由于传递的函数指针也使用了void*,所以比较苹果和梨的比较器的签名完全一样,如果传递了错误的函数指针,编译器也无能为力。

struct Apple {
   int weight;
};
struct Pear {
   double weight;
};
int compare_pears( void * pear1, void * pear2 ) {
   return static_cast<Pear*>(pear1)->weight - static_cast<Pear*>(pear2)->weight;
}
int main() {
   Apple apples[10];
   SortElements( apples, 20, sizeof(Pear), compare_pears );
}

虽然编译器能够验证函数指针的签名是否与函数所需的签名匹配,但函数指针本身是不安全的,并且允许您为基本上任何事情传递比较器。

将其与其他替代方案进行比较:

template <typename T, std::size_t N>
void SortElements( T (&array)[N], int (*cmp)( T const &, T const & ) );

这里编译器将从调用中推断出元素T 的类型和数组N 的大小。正如编译器所知,不需要传递T 的大小。传递给这个版本的SortElements 的比较器函数是强类型的:它采用两个常量引用存储在数组中的元素的类型并返回一个int。如果我们在之前的程序中尝试过:

int compare_pears( Pear const & lhs, Pear const & rhs );
int compare_apples( Apple const & l, Apple const & r );
Apple array[10];
//SortElements( array, compare_pears );   // Error!!!!
SortElements( array, compare_apples );    // Good!

你不能弄错数组的大小或元素的大小,如果有人改变了类型Apple,编译器会捡起来,如果数组的大小改变了,编译器会捡起来。您不能误认为传递给函数的比较器,因为编译器也会拾取它。现在程序是类型安全的,即使它使用函数指针(这可能会影响性能,因为它们禁止内联,这就是为什么std::sort 通常比qsort 快)

【讨论】:

  • 这是很好的解释。所以,基本上函数指针本质上是类型安全的,这取决于它们的使用方式是否会使它们做不安全的事情,就像我的例子一样?
  • 不,我不会这么说。函数指针是类型安全的。任何代码,不限于函数指针,在您开始转换时都可能是类型不安全的。是铸造使事情变得不安全。函数指针绝对没有与类型安全相关的特殊属性。
  • @FrankQ。代码中的类型安全问题来自函数接口的设计,它删除了类型信息,而不是因为您正在传递函数指针这一事实。通过隐式转换为void* 删除正确的类型意味着管理内存中每个位的类型的负担落在程序员(转换)的手中,而编译器没有任何帮助来验证正确性。另请注意,之前没有提到,该函数只能将数据作为位来管理,这意味着您将导致除 POD 之外的任何东西的未定义行为。
  • (即SortElements 可以移动位,但不能移动对象,它将无法应用用户定义的副本/移动)
【解决方案2】:

函数指针是类型安全的。然而,许多环境迫使程序员需要重铸它们。不正确的转换可能会导致严重的问题。

【讨论】:

    【解决方案3】:

    函数指针实际上经过类型检查并且是类型安全的。

    【讨论】:

      【解决方案4】:

      在 nesC(TinyOs 中使用的 C 方言)中强烈建议不要使用函数指针,因为它们会阻碍优化。在这里,静态代码分析(或者更确切地说是缺乏其适用性)是比类型安全更大的问题,但我不确定这些问题是否会混淆。

      另一个问题可能是使用函数指针作为事件处理程序。在使用通用事件调度程序时,您可能希望从正确的类型中抽象出来,这意味着您可以考虑将函数指针存储为 void* 只是为了模块化。这将是函数指针的类型不安全使用而不是类型安全的动态绑定使用的一个突出示例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-12
        • 1970-01-01
        • 2023-04-06
        • 2023-03-31
        • 2019-05-07
        • 1970-01-01
        • 1970-01-01
        • 2014-04-13
        相关资源
        最近更新 更多