C语言 K&R示例中所示的函数指针的用途是什么

nx7onnlm  于 2023-08-03  发布在  其他
关注(0)|答案(4)|浏览(65)

在K&R ANSI C书中,我偶然发现了一段代码,其中使用了指向函数的指针。我想我理解函数指针背后的思想,但是书中的例子对我来说没有意义。
功能是对文本行进行排序,使用快速排序算法. quicksort函数的声明如下所示:

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

字符串
到目前为止,这对我来说是完全有意义的,我们正在声明一个函数,其中一个参数是一个指向函数(comp)的指针,其中有两个void 指针参数。我假设我们这样做是为了保存内存,而不是复制实际的函数。
之后,函数在main中以这种方式使用:

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


这里是我有一些问题的地方:
1.为什么lineptr转换为(void**)
1.使用(numeric ? numcmp : strcmp)有什么用,是否意味着指向函数的指针也可以这样赋值:int ( * )(void*, void*) numcmp
编辑:lineptr的定义如下:char *lineptr[MAXLINES];我假设我们正在强制转换void**以将其从char类型更改为void指针类型

ql3eal8s

ql3eal8s1#

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中可能允许。

2g32fytz

2g32fytz2#

K&R(Brian W Kernighan and Dennis M里奇e0f1x 1988)中的这段代码不符合标准C语言,也不是当今编写代码的方式。
这是一个严重的指控-但C11标准说(§6.3转换,特别是在§6.3.2.3指针中)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()的调用就不需要对comparator参数进行强制转换。仍然需要对数组指针参数进行强制转换,因为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()读取行。这段代码可以在我的SOQ(Stack Overflow Questions)仓库中找到,在GitHub的src/sortfile子目录下的sortlines2.c文件中;还有一个X1 M25 N1 X,它的存储效率更高。这段代码让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()

iqjalb3h

iqjalb3h3#

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

  • 函数在用作值时可以隐式地转换为指向其自身的指针。所以这里(numeric ? numcmp : strcmp)实际上是(numeric ? &numcmp : &strcmp)
  • 指向任何类型的函数的指针可以被转换为指向另一类型的函数的指针。以后用一个错误的参数列表调用这个函数是愚蠢的。

这里我们有:

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

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

ncgqoxb0

ncgqoxb04#

为什么不这样做呢:
第一个月

一米

qsort需要处理某种类型的数组。它不能是void的数组。没有这样的事,虚空也不能比较。对于泛型函数,该数组必须是void指针数组。所以这里需要void *lineptr[] aka void **lineptr:指向多个指针中的第一个的指针。

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

嗯,这是不编译。

numeric ? &numcmp : &strcmp

你知道一个用作指针的数组如何退化为指向其第一个元素的指针吗?一个函数被用作指针,它会退化为一个指向函数的指针。因此,当用作参数时,numeric ? numcmp : strcmp等效于numeric ? &numcmp : &strcmp

相关问题