C语言中函数指针的澄清

12
以下代码来自示例 abo3.c,出自 Insecure Programming,另请参见为什么将 extern puts 强制转换为函数指针(void(*)(char*))&puts
int main(int argv,char **argc) {
    extern system,puts; 
    void (*fn)(char*)=(void(*)(char*))&system; // <==
    char buf[256];
    fn=(void(*)(char*))&puts;
    strcpy(buf,argc[1]);
    fn(argc[2]);
    exit(1);
}

具体来说,这一行:

void (*fn)(char*)=(void(*)(char*))&system;

我认为void (*fn)(char*)听起来像一个lambda表达式,但我知道它不是。那么,也许这只是一种使用括号的玩法,在这种情况下,void *fn(char*)是一个函数的声明,该函数引用了system?但是为什么(char*)参数没有名称?这是允许的吗?


4
“extern system, puts;” 这行代码让我有些担心。它声明了两个外部定义的整数(因为没有明确指定类型)。这些声明会遮盖 <stdlib.h> 和 <stdio.h> 中的声明。但是在链接时,这些函数很可能会被用来满足引用。请注意,在 C99 及其以上版本中,“extern” 行是无效的——在预标准或 C89/C90 标准的 C 中也是可疑的。 - Jonathan Leffler
9
您的声明 int main(int argv, char **argc) 技术上并不是无效的,但是完全不符合惯例。整数参数通常被称为 argc(参数计数),而 char 双指针通常被称为 argv(参数向量)。如果您要使用非标准名称,请不要为了其他人的健康使用颠倒的名称! - Jonathan Leffler
2
使用int (*fn)(char const *)=&system;,最好避免强制转换。 - 4566976
2
@alk 如果您拥有正确的函数指针,则不需要进行强制转换。将一个函数强制转换为另一个函数是不好的。我知道&是不必要的。 - 4566976
2
@NightSkyCode 绝对不是这样的。这些问题几乎有相同的标题,但内容完全不同。此外,将一个得分为-1的糟糕问题关闭为重复问题并不是一件好事。关闭为重复应该指向好问题和信息所在的位置(这就是为什么在这样做时不应考虑日期;一个较旧的问题可能比一个询问相同内容的较新问题更糟糕)。 - Bakuriu
显示剩余3条评论
7个回答

8

它声明变量fn为函数指针(指向一个具有一个参数类型为char *且不返回任何内容(void)的函数)。

该变量使用system的地址进行初始化-请参见http://linux.die.net/man/3/system。如从该页面所示,这将需要给定的强制转换。


1
由于system()返回的是int而不是void,这是否属于未定义行为? - Kevin
@Kevin - 你为什么认为这是未定义行为? - Ed Heal
1
因为您将函数转换为错误的类型。它不是 (void(*)(char*))反向转换是非法的,至少是这样的。 - Kevin
1
systemaddress,而不是值。 - Olipro
糟糕 - 没有加上 & - 这是不必要的。但话说回来,这段代码真烂 - Ed Heal
在这种情况下,'&'是必需的,因为'system'被声明为'extern system',这意味着'extern int system'。因此,如果您错过了'&',它将尝试取消引用'system'并获取整数。 - Kevin

4
然后,也许这只是一个带有括号的玩笑,其中void *fn(char *)是一个函数的声明,而这个函数正在引用系统,我想。void (*fn)(char *)不是函数,它是一个函数指针。您可以在此处查看有关函数指针的信息。并且()在这里很重要,您不能忽略它们。
但为什么参数(char*)没有名称?这是允许的吗?
是的,允许这样做。它的名称不是那么重要,但它的类型是。

void *fn(char *) 不定义函数指针。函数指针的定义是 void (*fn)(char *) - alk
@alk 其实这就是他在那一行所问的问题,他错过了问题代码中的 ()。我会让它更清晰明了。 - ameyCU

3

这是一个很简单的函数指针,它被赋予了系统调用的地址。


2

首先,您需要声明一个指向system函数的函数指针。

然后,您将其全部重定义为指向puts

接下来,似乎尝试对一个int进行索引,但是您已经颠倒了main的通常命名约定。

int main (int argc, char **argv)

先生,“argc”被声明为“char ** argc”。这可能会让人困惑。 - ameyCU
有人是怎么让 argcargv 这样排列的呢? - Tommy
1
@ameyCU 发现得很好,他颠倒了 main 函数的通常参数命名约定。 - Weather Vane
@Tommy 这是新手犯的错误(非常新手的错误) - PC Luddite

2
void (*fn)(char*)=(void(*)(char*))&system;

那行代码获取了被错误声明的符号system的地址(应该是int(const char*),而不是隐式的int),将其转换为fn类型并初始化该新局部变量。该类型是void(*)(char*)或指向接收单个char*并返回空值的函数的指针。

哦,那段代码有各种问题

  1. The traditional naming of the first two arguments to main is reversed:

    int main(int argv,char **argc)
    
  2. Declaring the standard-library-functions system and puts using "implicit int" as ints. That's not even the wrong function-type, but a data-type!

    extern system,puts;
    
  3. Casting the symbols address to a function-pointer is nearly sane. Now if only it was the right type... Anyway, on a box with data-pointers and code-pointers being the same size, it probably doesn't loose any information.

    void (*fn)(char*)=(void(*)(char*))&system; // <==
    fn=(void(*)(char*))&puts;
    
  4. Checking whether argv [!] is at least 2 should really not be omitted here:

    strcpy(buf,argc[1]);
    
  5. Calling a function through a function-pointer of wrong type is UB. Don't do that. Also, checking whether argv[!] is at least 3 before this point is not optional. fn(argc[2]);

  6. Relying on implicit declaration for strcpy and exit is also really bad. Neither has a prototype consistent with that! (they don't return int)


1
该行声明了一个函数指针变量,它接受一个参数并返回空值(void)。这与类型转换一起需要,以便与外部函数的原型相同,因为外部函数与void*指针类似,在编译期间不会提前绑定。

1

它是一个函数指针。如果您尝试使用文本菜单制作应用程序,而不是使用switch,我将使用函数指针来演示:

#include <stdio.h>
#include<unistd.h>

void clearScreen( const int x );
int exitMenu( void );
int mainMenu( void );
int updateSystem( void );
int installVlcFromPpa( void );
int installVlcFromSource( void );
int uninstallVLC( void );
int chooseOption( const int min, const int max );
void showMenu( const char *question, const char **options, int (**actions)( void ), const int length );
int installVLC( void );
int meniuVLC( void );
void startMenu( void );

int main( void ){
    startMenu();
    return 0;
}

void clearScreen( const int x ){
    int i = 0;
    for( ; i < x ; i++ ){
        printf( "\n" );
    }
}

int exitMenu( void ) {
    clearScreen( 100 );
    printf( "Exiting... Goodbye\n" );
    sleep( 1 );
    return 0;
}

int mainMenu( void ){
    clearScreen( 100 );
    printf( "\t\t\tMain Manu\n" );
    return 0;
}

int updateSystem( void ) {
    clearScreen( 100 );
    printf( "System update...\n" );
    sleep( 1 );
    return 1;
}

int installVlcFromPpa( void ) {
    clearScreen( 100 );
    printf("Install VLC from PPA \n");
    sleep( 1 );
    return 0;
}

int installVlcFromSource( void ) {
    clearScreen( 100 );
    printf( "Install VLC from Source \n" );
    sleep( 1 );
    return 0;
}

int uninstallVLC( void ) {
    clearScreen( 100 );
    printf( "Uninstall VLC... \n" );
    sleep( 1 );
    return 1;
}

int chooseOption( const int min, const int max ){
    int option,check;
    char c;

    do{
        printf( "Choose an Option:\t" );

        if( scanf( "%d%c", &option, &c ) == 0 || c != '\n' ){
            while( ( check = getchar() ) != 0 && check != '\n' );
            printf( "\tThe option has to be between %d and %d\n\n", min, max );
        }else if( option < min || option > max ){
            printf( "\tThe option has to be between %d and %d\n\n", min, max );
        }else{
            break;
        }
    }while( 1 );

    return option;
}

void showMenu( const char *question, const char **options, int ( **actions )( void ), const int length) {
    int choose = 0;
    int repeat = 1;
    int i;
    int ( *act )( void );

    do {
        printf( "\n\t %s \n", question );

        for( i = 0 ; i < length ; i++ ) {
            printf( "%d. %s\n", (i+1), options[i] );
        }

        choose = chooseOption( 1,length );
        printf( " \n" );

        act = actions[ choose - 1 ];
        repeat = act();

        if( choose == 3 ){
            repeat = 0;
        }
    }while( repeat == 1 );
}

int installVLC( void ) {
    clearScreen( 100 );
    const char *question = "Installing VLC from:";
    const char *options[10] = { "PPA", "Source", "Back to VLC menu" };
    int ( *actions[] )( void ) = { installVlcFromPpa, installVlcFromSource, mainMenu };

    size_t len = sizeof(actions) / sizeof (actions[0]);
    showMenu( question, options, actions, (int)len );
    return 1;
}

int meniuVLC( void ) {
    clearScreen( 100 );
    const char *question = "VLC Options";
    const char *options[10] = { "Install VLC.", "Uninstall VLC.", "Back to Menu." };
    int ( *actions[] )( void ) = { installVLC, uninstallVLC, mainMenu };

    size_t len = sizeof(actions) / sizeof (actions[0]);
    showMenu( question, options, actions, (int)len );

    return 1;
}

void startMenu( void ){
    clearScreen( 100 );
    const char *question = "Choose a Menu:";
    const char *options[10] = { "Update system.", "Install VLC", "Quit" };
    int ( *actions[] )( void ) = { updateSystem, meniuVLC, exitMenu };

    size_t len = sizeof(actions) / sizeof (actions[0]);

    showMenu( question, options, actions, (int)len );
}

编译并尝试一下。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接