【问题标题】:What is the use of a pointer to function as shown in K&R exampleK&R 示例中显示的函数指针的用途是什么
【发布时间】:2021-04-02 03:21:33
【问题描述】:

在 K&R ANSI C 书中,我偶然发现了一段代码,其中使用了指向函数的指针。我想我理解函数指针背后的想法,但是书中给出的示例对我来说没有意义。

该功能是使用快速排序算法对文本行进行排序。快速排序函数的声明如下:

void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *);

到目前为止,这对我来说非常有意义,我们正在声明一个函数,其中一个参数是指向具有两个 void 指针参数的函数 (comp) 的指针。我假设我们这样做是为了节省内存,而不是复制实际功能。

后面的函数在main中是这样使用的:

qsort((void **) lineptr, 0, nlines-1, (int (*)(void*, void*))(numeric ? numcmp : strcmp));

这里是我的一些问题:

  1. 为什么lineptr 转换为 (void **)
  2. 使用(numeric ? numcmp : strcmp)有什么用,是不是表示函数的指针也可以这样赋值:int ( * )(void*, void*) numcmp

编辑:lineptr 的定义如下:char *lineptr[MAXLINES]; 我假设我们正在转换 void** 以将其从 char 类型更改为 void 指针

【问题讨论】:

  • 给定的qsort函数用于通用排序,(numeric ? numcmp : strcmp));的含义取决于numeric的值,numcmp(数字比较)或strcmp(字符串比较)将被称为
  • 那为什么不这样做:qsort((void *) lineptr, 0, nlines-1, (int (numeric ? &numcmp : &strcmp)(void , 无效*)));
  • 不,它应该与定义中使用的相同指向一个函数的指针,该函数接受两个void*参数并返回int,检查here跨度>
  • 另外,qsort() prototype 实际上是void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); 注意const void * 类型,而不仅仅是void *。这可能会产生巨大的影响,尤其是当您尝试在 C++ 代码中使用 qsort() 时。
  • 当您引用书中的某些内容时,请附上完整的引文,包括页码。不要让人们花时间去寻找它。

标签: c function pointers


【解决方案1】:

K&R 书早于标准化 C,因此当时有效的一些结构不再有效。具体来说,它似乎假设任何两种指针类型之间的转换都是有效的。

lineptrchar * 的数组,当在表达式中使用时,该数组将衰减为指向其第一个元素的指针,即char **。因此需要转换为void ** 以匹配参数类型,因为只有在没有转换的情况下才能执行与void * 的转换。

表达式(numeric ? numcmp : strcmp) 选择一个函数作为comp 参数传递给函数,因此numcmpstrcmp 取决于numeric 的值。需要转换为(int (*)(void*, void*)),因为strcmp(可能是numcmp)的类型为int (*)(const char *, const char *)

转换为不同的函数指针类型并随后使用转换后的类型调用函数在标准 C 中是 undefined behavior,但在 K&R C 中可能允许这样做。

【讨论】:

    【解决方案2】:

    来自 K&R 的那段特定代码(Brian W Kernighan 和 Dennis M Ritchie — The C Programming Language, 2nd Edn 1988) 不符合标准 C 并且不是您现在编写代码的方式。

    这是一个严重的指控 - 但 C11 标准说 (§6.3 Conversions,特别是在 §6.3.2.3 Pointers ¶8:

    指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;结果应与原始指针比较。如果转换后的指针用于调用类型与引用类型不兼容的函数,则行为未定义。

    在书中,qsort() 函数有签名:

    void qsort(void *v[], int left, int right, int (*comp)(void *, void *));
    

    这与标准 C qsort() 的接口不同,而且是如履薄冰——您应该避免重新定义标准 C 函数,尤其是在重新定义具有不同接口的情况下。理想情况下,函数会被重命名,可能是quicksort()

    因此,作为comp 传递的函数指针应该与签名匹配

    int (*comp)(void *, void *);
    

    但是这两个函数都有签名:

    int numcmp(char *s1, char *s2);    /* Treating strings as numbers */
    int strcmp(char *s1, char *s2);
    

    qsort() 中的代码将函数指针视为int (*comp)(void *, void *),因此根据 §6.3.2.3 ¶8,代码具有未定义的行为。转换为公共类型很丑陋,但必须满足调用 qsort() 的要求,并且可以通过在被调用函数 (qsort()) 中转换回原始类型来撤消,但它不会转换函数指针。

    为了满足现代 C 的要求,numcmp() 函数需要重写:

    int numcmp(void *v1, void *v2)
    {
        double v1 = atof((char *)v1);
        double v2 = atof((char *)v2);
        if (v1 < v2)
            return -1;
        else if (v1 > v2)
            return +1;
        else
            return 0;
    }
    

    修改后的strcmp() 将被类似地重写——并重命名以避免与strcmp() 的标准 C 实现冲突。对qsort() 的调用则不需要对比较器参数进行强制转换。仍然需要对数组指针参数进行强制转换,因为 char ** 不会自动转换为 void **(但会转换为 void *)。

    现在,这是由 C 标准支持的理论观点。但是,在实践中,您通常会逃脱此类滥用(但编译器可能会生成有关滥用的警告)。

    在标准 C 中,qsort() 函数具有原型:

    void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
    

    这在几个方面有所不同,特别是比较器传递了const指针,而数组是void *而不是书中的void **

    上面的numcmp() 函数必须重写。如果您正在对字符串数组 (char **) 进行排序,这正是本书所做的,那么参数的类型为 char ** 转换为 void *

    这是一些对行数组进行排序的代码,使用 POSIX getline() 来读取行。此代码可在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 sortlines2.c 的形式在 src/sortfile 子目录中找到;还有一个sortlines3.c,它的内存效率更高。这段代码让getline() 为每一行分配新内存,这很愉快,但它分配的最小大小通常远大于您正在阅读的行。

    /*
    ** Originally a demonstration that a comparator in an SO answer was incorrect.
    ** Now a simple demonstration of reading a file and sorting.
    */
    
    #include "posixver.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* Correct comparator */
    static int compare(const void *p_lhs, const void *p_rhs)
    {
        const char *lhs = *(char **)p_lhs;
        const char *rhs = *(char **)p_rhs;
        return strcmp(lhs, rhs);
    }
    
    /* Lines in array are terminated by a newline */
    static void dump_array(const char *tag, size_t size, char *array[size])
    {
        printf("%s:\n", tag);
        for (size_t i = 0; i < size; i++)
            printf("%.2zu: %s", i, array[i]);
    }
    
    int main(void)
    {
        char **ptrs = 0;
        size_t numptrs = 0;
        char  *buffer = 0;
        size_t buflen = 0;
        size_t count = 0;
    
        while (getline(&buffer, &buflen, stdin) != -1)
        {
            if (count == numptrs)
            {
                size_t newnum = (numptrs + 2) * 2;
                void *newptrs = realloc(ptrs, newnum * sizeof(*ptrs));
                if (newptrs == 0)
                {
                    fprintf(stderr, "Out of memory (%zu bytes requested)\n", newnum * sizeof(*ptrs));
                    exit(1);
                }
                ptrs = newptrs;
                numptrs = newnum;
            }
            ptrs[count++] = buffer;
            buffer = 0;
            buflen = 0;
        }
    
        free(buffer);
    
        dump_array("Before sorting", count, ptrs);
        qsort(ptrs, count, sizeof(char *), compare);
        dump_array("After sort", count, ptrs);
    
        for (size_t i = 0; i < count; i++)
            free(ptrs[i]);
        free(ptrs);
    
        /* Avoid an 'in use at exit (reachable)' record in valgrind */
        /* Mac OS X El Capitan 10.11.4, Valgrind 3.12.0.SVN */
        /* macOS High Sierra 10.13.4, Valgrind 3.14.0.GIT */
        fclose(stdin);
    
        return 0;
    }
    

    请注意,qsort() 的参数没有强制转换——它们应该是不必要的。

    要编写数字比较器,您需要:

    int numcmp(const void *p1, const void *p2)
    {
        double v1 = atof(*(char **)p1);
        double v2 = atof(*(char **)p2);
        if (v1 < v2)
            return -1;
        else if (v1 > v2)
            return +1;
        else
            return 0;
    }
    

    然后简单地将numcmp(不带演员表)传递给qsort()

    【讨论】:

    • Re“仍然需要对数组指针参数进行强制转换,因为char ** 不会自动转换为void **(但会转换为void *)”:即使使用强制转换,也有即使考虑到 char *void * 必须具有相同的表示和相同的对齐要求,仍然会出现混叠违规和其他问题。
    【解决方案3】:

    qsort 调用使用 2 个 C 惯用点:

    • 当用作值时,函数可以隐式转换为指向自身的指针。所以这里(numeric ? numcmp : strcmp)实际上是(numeric ? &amp;numcmp : &amp;strcmp)
    • 可以将指向任何类型函数的指针转换为指向另一种类型函数的指针。只有 UB 稍后使用错误的参数列表调用函数。

    所以我们有:

    • 如果numeric 为True,则numcmp 用作比较函数,&amp;numcmp 被转换为一个指向函数的指针,该函数采用2 个void * 并返回一个int
    • 如果numeric 为False,则strcmp 用作比较函数,&amp;strcmp 被转换为一个指向函数的指针,该函数采用2 void * 并返回int

    对于第一个问题,lineptr 被转换为指向 void 的指针,因为它是 qsort 所期望的类型。这里的规则是指向对象的指针可以转换为指向另一种(对象)类型的指针,并且在转换回其真实类型时将获得其原始值。使用错误类型取消引用它只是UB。

    【讨论】:

    • 这样写同一段代码是不是错了:qsort((void *) lineptr, 0, nlines-1, (int ()(numeric ? numcmp : strcmp)(void*, void*)));如果是这样,你能告诉我为什么吗?
    【解决方案4】:

    那为什么不这样做呢:

    qsort((void *) lineptr, 0, nlines-1, (int (numeric ? &amp;numcmp : &amp;strcmp)(void, void*)));


    (void *) lineptr

    qsort 需要处理某种类型的数组。它不能是void 的数组。没有这种东西,虚无也无法比较。要使函数具有通用性,该数组必须是void 指针数组。所以这里需要void *lineptr[] aka void **lineptr:指向许多指针中第一个的指针。


    (int (numeric ? &amp;numcmp : &amp;strcmp)(void, void*))

    好吧,那是不编译的。


    numeric ? &amp;numcmp : &amp;strcmp

    您知道用作指针的数组如何退化为指向其第一个元素的指针吗?好吧,用作指针的函数会退化为指向该函数的指针。所以numeric ? numcmp : strcmp 等价于numeric ? &amp;numcmp : &amp;strcmp 作为参数使用。

    【讨论】:

      猜你喜欢
      • 2010-11-18
      • 2013-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-05
      • 2010-12-18
      • 2016-08-26
      • 1970-01-01
      相关资源
      最近更新 更多