【问题标题】:Incompatible pointer types passing in _Generic macro在 _Generic 宏中传递的指针类型不兼容
【发布时间】:2014-09-04 18:46:00
【问题描述】:

以下代码会生成 2 个警告,这些警告在问题的标题中有所描述。

#include <stdio.h>

static void _print_f(float *f){printf("float : %f\n", *f);}
static void _print_i(int *i)  {printf("int   : %d\n", *i);}

#define print(num) _Generic((num), \
    int*   : _print_i(num),        \
    float* : _print_f(num))


int main(void)
{
    print((&(int){10}));
    print((&(float){10.f}));

    return 0;
}

输出:

int   : 10
float : 10.000000

我知道,这个宏可以写成这样:

#define print(num) _Generic((num), \
    int*   : _print_i,             \
    float* : _print_f)(num)

在这种情况下,不会有任何警告,但是我的示例是一个虚拟的 sn-p,我写它来演示这个问题。在我的真实代码库中,我选择了前一种解决方案,因为需要将一些其他“默认”但类型特定的参数传递给所选函数。

所以问题是:即使宏正常工作,并且输出正是我所期望的,为什么会生成警告?


标志和环境:

/* Mac OS X 10.9.4
   Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn) */
cc -Wall -v -g -std=c11 -fmacro-backtrace-limit=0 -I/usr/local/include
   -c -o build/tmp/main.o main.c

更新1:

我忘了粘贴完整的回溯!这是第一个:

main.c:39:11: warning: incompatible pointer types passing 'int *'
to parameter of type 'float *' [-Wincompatible-pointer-types]
    print((&(int){10}));
          ^~~~~~~~~~~~
main.c:31:23: note: expanded from macro 'print'
    float* : _print_f(num))
                      ^
main.c:26:29: note: passing argument to parameter 'f' here
static void _print_f(float *f){printf("float : %f\n", *f);}
                            ^

这是第二个:

main.c:40:11: warning: incompatible pointer types passing 'float *'
to parameter of type 'int *' [-Wincompatible-pointer-types]
    print((&(float){10.f}));
          ^~~~~~~~~~~~~~~~
main.c:30:23: note: expanded from macro 'print'
    int*   : _print_i(num),        \
                      ^
main.c:27:27: note: passing argument to parameter 'i' here
static void _print_i(int *i)  {printf("int   : %d\n", *i);}
                          ^

更新 2:

clang 的开发人员修复此错误之前,这是一个使警告静音的丑陋解决方法,如果 assoc-list 中的所有键都是类型,或者都是指向类型的指针,则该解决方法将起作用;如果类型和指向类型的指针也在键中,则会失败:

/* HACK: re-casting pointers to mute warnings */
#define print(num) _Generic((num), \
    int*   : _print_i((int*)num),  \
    float* : _print_f((float*)num))

【问题讨论】:

  • _Generic 完全值得拥有自己的标签,但显然 StackOverflow 不会让我创建它。
  • @PascalCuoq 我做了,看看 => Flags & Envs
  • 看起来像是误报。 Clang 网站上的某处(clang-analyzer.llvm.org 底部)有一条评论,大意是他们将误报的排放视为错误,因此值得报告。
  • 您的解决方法在这种情况下有效,但不适用于演员本身触发警告的情况;请参阅我发布的示例。
  • @KeithThompson 很有趣。如果 assoc-list 中的所有键都是指向类型的指针或者都是裸类型,我的解决方法就有效。我将您的示例更改为 intfloatprint_i((int)i)print_f((float)f) 并且它也适用于它。如果 assoc-list 的键是裸类型和指向类型的指针的混合,我的 hack 只会失败.

标签: c macros printf clang c11


【解决方案1】:

这不是 clang 中的错误,但不幸的是 C11 标准所要求的。 _Generic 主表达式的所有分支都必须是有效的表达式,因此在所有情况下都有效。只有一个分支会被评估这一事实与此无关。

您的替代版本是 C11 对以下情况的预见:选择函数(而不是评估的调用)作为类型泛型表达式的结果,并将该函数应用于参数。

【讨论】:

  • 你所说的问题——尽管你提到它与分支无关——是在 6.5.1.1§3: “通用选择的控制表达式没有被评估。” ... “没有任何来自任何其他通用选择的通用关联的表达式被评估。” => 所以它根本不可能是“无效的” ,因为实际上没有参数传递给赋值表达式!无论如何,在这种情况下,整个“有效性”太抽象了,还想知道标准在哪里指定了赋值表达式的“有效性”?
  • @PeterVaro,有问题的表达式必须是语法树中指定的“赋值表达式”。如果它们不是这样的表达式,则它们是无效的。未计算的表达式必须在语法上仍然正确。 sizeof 表达式也一样。在sizeof *a 中,子表达式*a 不会被计算,所以如果a 是一个空指针,程序就不会崩溃。尽管如此,*a 必须是一个有效的表达式,这样编译器才能正确解析它,并且还可以确定结果类型(以及它的大小)。
  • 现在我有点理解您所说的“有效”表达式的含义,这可能也解释了为什么当我直接重新转换 num 变量时我的解决方法有效。但是,没有解释为什么解决方法不能同时对 assoc-list 中的类型和指向类型的指针起作用!无论如何,我也不明白的是为什么调用一个函数并将一个变量作为具有已知类型(基本上 == 有效语法)的参数首先不被识别为有效(_print_i(num))?
  • @PeterVaro,第 6.5.2.2 节“函数调用”有一个“约束”,要求函数调用的参数必须具有函数原型指定的正确类型。任何“违反约束”都会使程序无效并需要编译器进行诊断。
  • 这当然对“常规”函数调用有效,但是在“条件”函数调用中,我相信它不必满足这些要求。你看,如果只有一个赋值表达式会计算(根据标准是这种情况),num 无法访问具有不同参数类型的函数,因此所有语句在它们“自己的情况”下都是有效的,所以这个对我来说仍然看起来像一个错误,而不是一个正确的实现。
【解决方案2】:

更正:这不是(据我所知)clang 中的错误,而是对_Generic 应该如何表现的正确解释。 generic-selection 表达式中只有一个通用关联被求值,但它们都必须是有效的表达式。 (_Generic 不像宏。)

Jens Gustedt's answer


这绝对看起来像clang中的错误。我有理由确定您的代码是有效的 C11。我在 Linux Mint 上的 3.4 版中看到了同样的情况。

我整理了一个slightly simplified demo

#include <stdio.h>

static void print_i(int i)   { puts("int"); }
static void print_ip(int *i) { puts("int*"); }

#define print(num) _Generic((num), \
    int    : print_i(num),         \
    int*   : print_ip(num))

int main(void) {
    int i = 10;
    print(i);
    print(&i);
}

输出正确,但我收到以下警告:

c.c:12:11: warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'int *'; take the address with & [-Wint-conversion]
    print(i);
          ^
          &
c.c:8:23: note: expanded from macro 'print'
    int*   : print_ip(num))
                      ^
c.c:4:27: note: passing argument to parameter 'i' here
static void print_ip(int *i) { puts("int*"); }
                          ^
c.c:13:11: warning: incompatible pointer to integer conversion passing 'int *' to parameter of type 'int'; remove & [-Wint-conversion]
    print(&i);
          ^~
c.c:7:22: note: expanded from macro 'print'
    int    : print_i(num),         \
                     ^
c.c:3:25: note: passing argument to parameter 'i' here
static void print_i(int i)   { puts("int"); }
                        ^
2 warnings generated.

【讨论】:

  • @PeterVaro:我还没有报告。随意这样做。
  • 不,这不是clang中的错误,而是对C11标准的正确解释。
  • @JensGustedt:请详细说明。
  • @KeithThompson,你看到我的回答了吗?
  • @JensGustedt:我现在有(我以前错过了)。我相信你是对的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
相关资源
最近更新 更多