Dam*_*ski 4 c pointers function
在 K&R ANSI C 书中,我偶然发现了一段代码,其中使用了指向函数的指针。我想我理解函数指针背后的想法,但书中给出的例子对我来说没有意义。
该功能是使用快速排序算法对文本行进行排序。快速排序函数的声明如下所示:
void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *);
Run Code Online (Sandbox Code Playgroud)
到目前为止,这对我来说非常有意义,我们声明了一个函数,其中一个参数是指向具有两个空指针参数的函数 ( comp) 的指针。我假设我们这样做是为了节省内存,而不是复制实际功能。
稍后在 main 中以这种方式使用该函数:
qsort((void **) lineptr, 0, nlines-1, (int (*)(void*, void*))(numeric ? numcmp : strcmp));
Run Code Online (Sandbox Code Playgroud)
这是我有一些问题的地方:
lineptr强制转换为 (void **)(numeric ? numcmp : strcmp),是否意味着也可以像这样分配指向函数的指针:int ( * )(void*, void*) numcmp编辑:lineptr定义如下:char *lineptr[MAXLINES];我假设我们正在void**将其从 char 转换为类型 void 指针
K&R(Brian W Kernighan 和 Dennis M Ritchie \xe2\x80\x94\n The C 编程语言,第 2 版1988 年)中的那段特定代码不符合标准 C,并且不是您现在应该编写代码的方式。
\n这是一个严重的指控 \xe2\x80\x94 但 C11 标准说(\xc2\xa76.3 Conversions,特别是\xc2\xa76.3.2.3 Pointers \xc2\xb68:
\n\n\n指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再转换回来;结果应等于原始指针。如果转换后的指针用于调用其类型与引用类型不兼容的函数,则行为未定义。
\n
在书中,该qsort()函数的签名为:
void qsort(void *v[], int left, int right, int (*comp)(void *, void *));\nRun Code Online (Sandbox Code Playgroud)\n这与标准 C 的接口不同qsort(),并且如履薄冰 \xe2\x80\x94 您应该避免重新定义标准 C 函数,特别是如果重新定义具有不同的接口。理想情况下,该函数将被重命名,也许会被重命名为quicksort().
因此,传递的函数指针comp应该与签名匹配
int (*comp)(void *, void *);\nRun Code Online (Sandbox Code Playgroud)\n但这两个函数都有签名:
\nint numcmp(char *s1, char *s2); /* Treating strings as numbers */\nint strcmp(char *s1, char *s2);\nRun Code Online (Sandbox Code Playgroud)\n内部代码qsort()将函数指针视为int (*comp)(void *, void *),因此根据 \xc2\xa76.3.2.3 \xc2\xb68,代码具有未定义的行为。转换为通用类型虽然丑陋,但却是满足调用要求所必需的qsort(),并且可以通过在被调用函数 () 中转换回原始类型来撤消qsort(),但它不会转换函数指针。
为了满足现代 C 的要求,numcmp()需要重写该函数:
int numcmp(void *v1, void *v2)\n{\n double v1 = atof((char *)v1);\n double v2 = atof((char *)v2);\n if (v1 < v2)\n return -1;\n else if (v1 > v2)\n return +1;\n else\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n修订版strcmp()将被类似地重写 \xe2\x80\x94 并重命名以避免与strcmp(). 对 then 的调用qsort()不需要对比较器参数进行强制转换。仍然需要对数组指针参数进行强制转换,因为char **不会自动转换为void **(但会转换为void *)。
现在,这就是 C 标准支持的理论观点 \xe2\x80\x94。然而,在实践中,您经常会逃脱这种滥用(但编译器可能会生成有关滥用的警告)。
\n在标准 C 中,该qsort()函数的原型为:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));\nRun Code Online (Sandbox Code Playgroud)\n这在几个方面有所不同,值得注意的是比较器是通过const指针传递的,而数组并不void *像void **书中那样。
上面的函数numcmp()必须重写。如果您正在对字符串数组 ( char **) 进行排序(这正是本书所做的),那么参数的类型将char **强制转换为void *。
下面是一些对行数组进行排序的代码,使用 POSIXgetline()读取行。此代码可在我的GitHub 上的SOQ(Stack Overflow Questions)存储库中作为src/sortfile子目录sortlines2.c中的文件找到;还有一种内存效率更高。这段代码为每一行分配了新的内存,它做得很高兴,但它分配的最小大小通常远远大于您正在读取的行。sortlines3.cgetline()
/*\n** Originally a demonstration that a comparator in an SO answer was incorrect.\n** Now a simple demonstration of reading a file and sorting.\n*/\n\n#include "posixver.h"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/* Correct comparator */\nstatic int compare(const void *p_lhs, const void *p_rhs)\n{\n const char *lhs = *(char **)p_lhs;\n const char *rhs = *(char **)p_rhs;\n return strcmp(lhs, rhs);\n}\n\n/* Lines in array are terminated by a newline */\nstatic void dump_array(const char *tag, size_t size, char *array[size])\n{\n printf("%s:\\n", tag);\n for (size_t i = 0; i < size; i++)\n printf("%.2zu: %s", i, array[i]);\n}\n\nint main(void)\n{\n char **ptrs = 0;\n size_t numptrs = 0;\n char *buffer = 0;\n size_t buflen = 0;\n size_t count = 0;\n\n while (getline(&buffer, &buflen, stdin) != -1)\n {\n if (count == numptrs)\n {\n size_t newnum = (numptrs + 2) * 2;\n void *newptrs = realloc(ptrs, newnum * sizeof(*ptrs));\n if (newptrs == 0)\n {\n fprintf(stderr, "Out of memory (%zu bytes requested)\\n", newnum * sizeof(*ptrs));\n exit(1);\n }\n ptrs = newptrs;\n numptrs = newnum;\n }\n ptrs[count++] = buffer;\n buffer = 0;\n buflen = 0;\n }\n\n free(buffer);\n\n dump_array("Before sorting", count, ptrs);\n qsort(ptrs, count, sizeof(char *), compare);\n dump_array("After sort", count, ptrs);\n\n for (size_t i = 0; i < count; i++)\n free(ptrs[i]);\n free(ptrs);\n\n /* Avoid an \'in use at exit (reachable)\' record in valgrind */\n /* Mac OS X El Capitan 10.11.4, Valgrind 3.12.0.SVN */\n /* macOS High Sierra 10.13.4, Valgrind 3.14.0.GIT */\n fclose(stdin);\n\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n请注意,\xe2\x80\x94 的参数没有强制转换,qsort()它们应该是不必要的。
要编写数字比较器,您需要:
\nint numcmp(const void *p1, const void *p2)\n{\n double v1 = atof(*(char **)p1);\n double v2 = atof(*(char **)p2);\n if (v1 < v2)\n return -1;\n else if (v1 > v2)\n return +1;\n else\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n然后简单地传递numcmp(不进行强制转换)到qsort().
K&R 书籍早于标准化 C,因此当时有效的一些构造不再有效。具体来说,似乎假设任何两种指针类型之间的转换都是有效的。
lineptr是 的数组char *,当在表达式中使用时,该数组将衰减为指向其第一个元素的指针,即char **。因此void **需要强制转换来匹配参数类型,因为只有void *在没有强制转换的情况下才能执行到/从的转换。
表达(numeric ? numcmp : strcmp)是选择一个函数来传递作为comp参数的功能,所以无论是numcmp或strcmp取决于的值numeric。(int (*)(void*, void*))需要强制转换,因为strcmp(并且大概numcmp)具有 type int (*)(const char *, const char *)。
转换为不同的函数指针类型并随后使用转换类型调用函数在标准 C 中是未定义的行为,但在 K&R C 中可能是允许的。
该qsort调用使用了 2 个 C 惯用点:
(numeric ? numcmp : strcmp)实际上是(numeric ? &numcmp : &strcmp)所以这里我们有:
numeric为 True,则numcmp用作比较函数,并被&numcmp转换为指向函数的指针,该函数取 2void *并返回intnumeric为 False,则strcmp用作比较函数,并被&strcmp转换为指向函数的指针,该函数取 2void *并返回int对于第一个问题,lineptr被转换为指向 void 的指针,因为它是 所期望的类型qsort。这里的规则是指向对象的指针可以转换为指向另一种类型(对象)的指针,并且当转换回其真实类型时它将获得其原始值。只有 UB 使用错误的类型来取消引用它。