Используя функцию со ссылкой как функцию с указателями? - PullRequest
2 голосов
/ 08 апреля 2010

Сегодня я наткнулся на кусок кода, который показался мне ужасным.Кусочки обсуждались в разных файлах, я попытался описать их суть в простом тестовом примере ниже.База кода регулярно сканируется с помощью FlexeLint ежедневно, но эта конструкция закладывается в код с 2004 года.

Дело в том, что функция, реализованная с передачей параметров с использованием ссылок, вызывается как функция спередача параметров с использованием указателей ... из-за приведения функции.Конструкция работает с Irix с 2004 года, и теперь при переносе она действительно работает и в 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 ]

3 голосов
/ 08 апреля 2010

Что вы думаете?

Уберите это.Это неопределенное поведение и, следовательно, бомба, которая может взорваться в любое время.Новая версия платформы или компилятора (или фазы Луны, в этом отношении) может отключить его.

Конечно, я не знаю, как выглядит настоящий код, но из вашей упрощенной версии кажется, что проще всего было бы дать string_struct неявный конструктор, принимающий const char*, templatize ftest() на аргумент указателя на функцию и удалите все приведения.

2 голосов
/ 08 апреля 2010

Это, очевидно, ужасная техника, формально это неопределенное поведение и серьезная ошибка при вызове функции через несовместимый тип, но на практике она должна «работать» в нормальной системе.

На уровне машины ссылка и указатель имеют абсолютно одинаковое представление;они оба просто адрес чего-то.Я вполне ожидал бы, что fptr и fref скомпилируют одно и то же, инструкция для инструкции, на любом компьютере, на котором вы могли бы достать.Ссылку в этом контексте можно просто рассматривать как синтаксический сахар;указатель, который автоматически разыменовывается для вас.На уровне машины они точно такие же.Очевидно, что могут быть какие-то неясные и / или несуществующие платформы, где это может быть не так, но в целом это верно в 99% случаев.

Кроме того, на большинстве распространенных платформ все указатели объектов имеют одинаковое представление, как и все указатели функций.То, что вы сделали, на самом деле ничем не отличается от вызова функции, ожидающей int через тип long, на платформе, где эти типы имеют одинаковую ширину.Это формально незаконно и почти гарантированно работает.

Из определения 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, но трудно представить себе разумную реализацию, которая могла бы сделать это ложным.

Как говорится, не делай этого!

1 голос
/ 08 апреля 2010

Давайте просто согласимся, что это очень уродливо, и вы собираетесь изменить этот код.С актерами вы обещаете, что убедитесь, что типы совпадают, а они явно нет.По крайней мере, избавьтесь от броска в стиле C.

1 голос
/ 08 апреля 2010

работает по чистой случайности.

fptr ожидает const char *, а fref ожидает string_struct &.

Структура struct string_struct имеет ту же структуру памяти, что и const char *, поскольку она содержит только 256-байтовый массив символов и не имеет виртуальных членов.

В c ++ вызовите по ссылке, например, string_struct & реализуется передачей скрытого указателя на ссылку, поэтому в стеке вызовов он будет таким же, как если бы он был передан как истинный указатель.

Но если структура string_struct изменится, все сломается, поэтому код вообще не считается безопасным. Также это зависит от реализации компилятора.

...