【问题标题】:Using a function with reference as a function with pointers?使用带引用的函数作为带指针的函数?
【发布时间】:2011-02-05 15:47:54
【问题描述】:

今天我偶然发现了一段令我感到恐惧的代码。这些片段在不同的文件中喋喋不休,我尝试在下面的一个简单的测试用例中写下它的要点。每天都会使用 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;
}

【问题讨论】:

  • 啊啊啊啊!为什么有人会使用这样的代码?
  • @Red-nosed unicorn - 我不认为这是一个故意的构造。该代码用于网络包处理程序中,我猜 c++ 参考传递意外是由一个热心的 c++ 开发人员进入这部分的。

标签: c++ casting pass-by-reference parameter-passing


【解决方案1】:

这纯属偶然。

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

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

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      你觉得呢?

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

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

      【讨论】:

        【解决方案4】:

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

        在机器级别,引用和指针具有完全相同的表示;它们都只是某物的地址。我完全希望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 并没有正式要求,但很难想象一个理智的实现可以使那些错误。

        话虽如此,不要这样做!

        【讨论】:

        • 由于某种原因被重复发布。有人请帮我删除其中一个
        • 我倾向于给您“正确答案”,因为我喜欢您与 malloc 的类比,并且在使用级别上,决定什么被认为是“正确”的是我们开发人员。但是,尽管这在这里和现在都有效,而且你可能是对的,它可以在“我能拿到的任何计算机上”工作,但我很难相信没有证据的这样的陈述。我认为 Ernelli 说的更可信It works by pure chance。它没有被定义为这样实现,但偶然它已经并且通常是......
        猜你喜欢
        • 2021-10-01
        • 1970-01-01
        • 2014-03-29
        • 2011-10-27
        • 2012-01-04
        • 1970-01-01
        • 2017-07-23
        • 2013-05-21
        • 1970-01-01
        相关资源
        最近更新 更多