2

今天我偶然发现了一段让我感到恐惧的代码。这些片段在不同的文件中喋喋不休,我尝试在下面的一个简单测试用例中写下它的要点。代码库每天都会使用 FlexeLint 进行例行扫描,但这种结构自 2004 年以来一直存在于代码中。

问题是使用引用传递参数实现的函数被称为使用指针传递参数的函数......由于函数转换。该构造自 2004 年以来一直在 Irix 上运行,现在在移植时它实际上也可以在 Linux/gcc 上运行。

我现在的问题。这是一个可以信任的构造吗?我可以理解编译器构造函数是否实现了引用传递,因为它是一个指针,但它可靠吗?是否存在隐患?

我应该更改fref(..)使用指针并冒险在此过程中制动任何东西吗?

你怎么看?

编辑

在实际代码中,两者都fptr(..)使用fref(..)相同的struct- 更改下面的代码以更好地反映这一点。

#include <iostream>
#include <string.h>

using namespace std;

// ----------------------------------------
// This will be passed as a reference in fref(..)

struct string_struct {
    char str[256];
};

// ----------------------------------------
// Using pointer here!

void fptr(string_struct *str) 
{
    cout << "fptr: " << str->str << endl;
}

// ----------------------------------------
// Using reference here!

void fref(string_struct &str) 
{
    cout << "fref: " << str.str << endl;
}

// ----------------------------------------
// Cast to f(const char*) and call with pointer

void ftest(void (*fin)()) 
{
    string_struct str;
    void (*fcall)(void*) = (void(*)(void*))fin;
    strcpy(str.str, "Hello!");
    fcall(&str);
}

// ----------------------------------------
// Let's go for a test

int main() {
    ftest((void (*)())fptr); // test with fptr that's using pointer 
    ftest((void (*)())fref); // test with fref that's using reference
    return 0;
}
4

4 回答 4

3

你怎么看?

清理。这是未定义的行为,因此是随时可能爆炸的炸弹。一个新的平台或编译器版本(或月相,就此而言)可能会绊倒它。

当然,我不知道真正的代码是什么样的,但从您的简化版本看来,最简单的方法似乎是给string_struct一个隐式构造函数const char*,在函数指针参数上模板化ftest(),并删除所有涉及的强制转换。

于 2010-04-08T11:30:06.920 回答
2

这显然是一种可怕的技术,从形式上讲,通过不兼容的类型调用函数是未定义的行为和严重的错误,但它实际上应该在正常系统上“工作”。

在机器级别,引用和指针具有完全相同的表示;它们都只是某物的地址。我完全期望这一点,fptrfref在任何你可以拿到手的计算机上编译成完全相同的东西,一个指令一个指令。在这种情况下,引用可以简单地被认为是语法糖。为您自动取消引用的指针。在机器级别,它们完全相同。显然,可能有一些晦涩和/或已失效的平台可能并非如此,但一般来说,99% 的情况都是如此。

此外,在大多数常见平台上,所有对象指针都具有相同的表示形式,所有函数指针也是如此。您所做的实际上与在这些类型具有相同宽度的平台上通过需要很长时间的类型调用期望 int 的函数并没有什么不同。它在形式上是非法的,而且几乎可以保证有效。

甚至可以从定义中推断出malloc所有对象指针都具有相同的表示;我可以malloc存储大量内存,并将我喜欢的任何(C 风格)对象放在那里。由于malloc只返回一个值,但该内存可以用于我喜欢的任何对象类型,很难看出不同的对象指针如何合理地使用不同的表示,除非编译器为每种可能的类型维护大量的值表示映射.

void *p = malloc(100000);
foo  *f =  (foo*)p;  *f = some_foo;  
bar  *b =  (bar*)p;  *b = some_bar;
baz  *z =  (baz*)p;  *z = some_baz; 
quux *q =  (quux*)p; *q = some_quux;  

(丑陋的演员表在 C++ 中是必要的)。以上是工作所必需的。因此,虽然我不认为事后正式要求这样做memcmp(f, b) == memcmp(z, q) == memcmp(f, q) == 0,但很难想象一个理智的实施可以使那些错误。

话虽如此,不要这样做!

于 2010-04-08T13:03:50.473 回答
1

它纯属偶然。

fptr 需要一个 const char * 而 fref 需要一个 string_struct &。

struct string_struct 与 const char * 具有相同的内存布局,因为它只包含一个 256 字节的 char 数组,并且没有任何虚拟成员。

在 c++ 中,通过引用调用,例如 string_struct & 是通过将隐藏指针传递给引用来实现的,因此在调用堆栈上它与作为真指针传递时相同。

但是如果结构 string_struct 发生变化,一切都会中断,因此代码根本不被认为是安全的。它还取决于编译器的实现。

于 2010-04-08T11:22:20.870 回答
1

让我们同意这是非常丑陋的,您将更改该代码。使用演员表,您保证您确保类型匹配并且它们显然不匹配。至少摆脱 C 风格的演员阵容。

于 2010-04-08T11:22:48.943 回答