Можно ли бросить указатель на функцию одного типа к функции другого типа, который берет дополнительные аргументы?

Можете Вы бросать указатель функции этого типа:

void (*one)(int a)

к одному из этого типа:

void (*two)(int a, int b)

и затем безопасно вызовите указанный функция с дополнительными аргументами (аргументами), которые она была брошена для взятия? Я думал, что такая вещь была недопустима, это, оба функциональных типа должны были быть совместимыми. (Значение того же прототипа - то же возвращаемое значение, тот же список параметров.), Но это точно, что этот бит GTK + код, кажется, делает (взятый отсюда):

g_signal_connect_swapped(G_OBJECT(button), "clicked",
                         G_CALLBACK(gtk_widget_destroy), G_OBJECT(window));

Если Вы будете искать "нажатый" сигнал (или просто посмотрите на другие примеры его использования из первой ссылки), то Вы будете видеть, что его обработчики, как ожидают, будут объявлены как это:

void user_function(GtkButton *button, gpointer user_data);

При регистрации обработчика через g_signal_connect_swapped (), указатель виджета и аргументы указателя данных подкачиваются в порядке, таким образом, объявление должно быть похожим на это вместо этого:

void user_function(gpointer user_data, GtkButton *button);

Вот проблема. gtk_widget_destroy () функция, зарегистрированная как обратный вызов, смоделирован как это:

void gtk_widget_destroy(GtkWidget *widget);

взять только отдельный аргумент. По-видимому, потому что указатель данных (GtkWindow) и указатель на сигнальный виджет (GtkButton) подкачиваются, единственным аргументом, который он получает, будет указатель окна, и указатель кнопки, который будет передан после, будет тихо проигнорирован. Некоторый поиск с помощью Google поднял подобные примеры, даже регистрация функций как gtk_main_quit (), которые не берут аргументов вообще.

Я корректен в том, чтобы полагать, что это нарушение стандартов? Имеют GTK +, разработчики нашли некоторое легальное волшебство сделать эту всю работу?

6
задан Pete 7 January 2010 в 20:38
поделиться

3 ответа

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

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

2
ответ дан 17 December 2019 в 07:05
поделиться

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

Хотя сами объекты содержат примитивы, там место хранения находится место назначения фактического объекта, находящегося в куче.

Кроме того, что ваше заявление довольно хорошо написано. У вас есть конкретный вопрос, который я пропустил:)?

-121--2805577-

Вот как вы думаете о виртуальных методах. Каждый экземпляр класса имеет «поля» для хранения методов. Когда вы помечаете метод как виртуальный , он говорит сделать новый «ящик» и поместить в него метод. Когда метод помечается как override в производном классе, он сохраняет «box» из базового класса, но помещает в него новый метод.

Здесь имеется класс A и метод с именем mVVirtual , помеченный как virtual . Это означает, что создайте новый «ящик» с именем mVVirtual и поместите в него метод с определением

Console.WriteLine("A::mVVirtual"); 

Тогда у вас есть производный класс B и метод с именем mVVirtual , который помечен как virtual . Это говорит сделать новый «ящик» с именем mVVirtual и поместить в него метод с определением

Console.WriteLine("B::mVVirtual"); 

В частности, «ящик», унаследованный от A , скрыт! Его не могут видеть объекты, которые набраны как B , или классы, производные от B .

Имеется производный класс C и метод с именем mVVirtual , помеченный как override . Это говорит взять «ящик» с именем mVVirtual , унаследованный от B , и поместить в него другой метод с определением

Console.WriteLine("C::mVVirtual"); 

Теперь, когда у вас есть

B b1 = new C(); 
b1.mVVirtual();

вы говорите компилятору, что b1 является B так что b1.mVVirtual() ищет в «box» mVVirtual и находит метод с определением

Console.WriteLine("C::mVVirtual"); 

, поскольку b1 действительно является C , и именно это находится в «box» mVVirtual для экземпляров C .

Но когда у вас есть

A a2 = new C(); 
a2.mVVirtual();

вы говорите компилятору, что a2 является A и поэтому он смотрит в «поле» и находит

Console.WriteLine("A::mVVirtual");

Компилятор не может знать, что a2 действительно является C (вы ввели его как A ), поэтому не известно, что a2 действительно является экземпляром класса, производным от класса, который скрыл «box» mVVirtual , определенный A . Известно, что A имеет «box» с именем mVVirtual и поэтому выдает код для вызова метода в этом «box».

Таким образом, чтобы попытаться поставить это лаконично:

class A {
    public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}  

определяет класс, который имеет «box» с полным именем A:: mVVirtual , но что вы можете сослаться на mVVirtual .

class B : A 
{
    // "new" method; compiler will tell you that this should be marked "new" for clarity.
    public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}  

определяет класс, который имеет "поле" с полным именем B:: mVVirtual , но на который можно ссылаться под именем mVVirtual . Ссылка на B.mVVirtual не относится к "ящику" с полным именем A:: mVVirtual ; это "поле" не может быть отображено объектами, набранными как B (или классами, производными от B ).

class C : B
{
    public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}  

определяет класс, который принимает "box" с полным именем B:: mVVirtual и помещает в него другой метод.

Тогда

A a2 = new C(); 
a2.mVVirtual();

говорит, что a2 является A , так что a2.mVVirtual выглядит в "поле" с полным именем A:: mVVirtual и вызывает метод в этом "поле". Вот почему вы видите

A::mVVirtual

на консоли.

Существует два других аннотатора методов. аннотация делает новое "поле" не помещает определение метода в "поле". new создает новый "box" и помещает определение метода в "box", но не позволяет производным классам поместить свои собственные определения метода в "box" (используйте virtual , если вы хотите это сделать).

Извините за долголетие, но я надеюсь, что это поможет.

-121--3566144-

На мой взгляд, C89 стандарты в этом отношении довольно запутаны. Насколько я знаю, они не запрещают кастинг от/к функции без спецификации param, поэтому:

typedef void (*one)(int first);
typedef void (*two)(int first, int second);
typedef void (*empty)();

one src = something;
two dst;

/* Disallowed by C89 standards */
dst = (two) src;

/* Not disallowed by C89 standards */
dst = (two) ((empty) src);

В конце компиляторы должны уметь приводить от одного к двум , поэтому я не вижу причины запрещать прямой актёрский состав.

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

1
ответ дан 17 December 2019 в 07:05
поделиться

Теперь, насколько круто в том, что я также в настоящее время проходит свой путь через учебное пособие GTK и наткнулся на такую ​​же проблему.

Я попробовал несколько примеров, а затем задал вопрос Что произойдет, если я бросил указатель функции, изменив количество параметров , с упрощенным примером.

Ответ на ваш вопрос (адаптирован из прекрасных ответов на мой вопрос выше, а ответы от вопросов указатель функции отлив на разную подпись ):

  • Да, это нарушение C стандартный Если вы бросаете указатель функции на функцию указателя несовместимого типа, вы должны вернуться к его оригинальному (или совместимому) типу, прежде чем вызывать его. Все остальное - неопределенное поведение.
  • Тем не менее, то, что на практике он на практике зависит от компилятора C, особенно вызывающей конвенции, которую он использует. Наиболее распространенной вызовой конвенцией (по крайней мере, на I386) происходит просто поставить параметры в стеке в обратном порядке, поэтому, если функция нуждается в меньшем количестве параметров, чем прилагается, она просто будет использовать первые параметры и игнорировать остальные - какие Только то, что вы хотите. Это будет , однако на платформах с различными соглашениями о вызовах.

Итак, реальный вопрос, почему разработчики Glib это понравилось. Но это другой вопрос, который я думаю ...

1
ответ дан 17 December 2019 в 07:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: