为什么我没有得到一个指向int的指针的警告?

Kar*_*eem 6 c int pointers casting

下面的代码提供了一个警告,用于从指向int的指针进行强制转换

int foo[] = {1, 2, 3};
#define bar(x) foo[(int)(x) % 3]
char *baz = "quux";
bar(baz);
Run Code Online (Sandbox Code Playgroud)

另一方面,以下代码编译时没有任何警告(无论它是否可能导致运行时错误)

#include <ctype.h>
// some code
char *baz = "quux";
isalpha(baz);
Run Code Online (Sandbox Code Playgroud)

当我打开ctype.h来查看时isalpha,我发现它是一个使用其他几个宏的宏

#  define _ISbit(bit)   (1 << (bit))

enum
{
    // some identifiers
    _ISalpha = _ISbit (2),
    // some identifiers
};

extern const unsigned short int **__ctype_b_loc (void)
 __THROW __attribute__ ((__const__));

# define __isctype(c, type) \
((*__ctype_b_loc ())[(int) (c)] & (unsigned short int) type)

# define isalpha(c) __isctype((c), _ISalpha)
Run Code Online (Sandbox Code Playgroud)

正如你可能会看到,扩大的结果,isalpha宏观还是明确地把指针bazint.但是,编译时不会发出任何警告.因此很明显的代码两件执行相同的操作(例如,铸造了char *一个int),然而,人们给出了一个警告,其他没有.为什么?

注意:使用具有相同选项的编译命令来编译包含这些代码段的程序.

编译器版本:

gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Run Code Online (Sandbox Code Playgroud)

oua*_*uah 9

第一个程序没有发出警告,gcc 4.7但会对版本发出警告4.8.

第二个程序不会发出警告,因为宏定义位于系统头中.添加-Wsystem-headers以获取第二个程序的警告.

来自gcc文档(强调我的)

-Wsystem报头

打印系统头文件中找到的构造的警告消息. 系统头的警告通常被抑制,假设它们通常不表示实际问题并且只会使编译器输出更难读.


Kei*_*son 5

C 标准允许将任何标准库函数额外实现为宏。这种宏定义的要求(N1570第 7.1.4 节)是:

在头文件中声明的任何函数都可以额外实现为在头文件中定义的类函数宏,因此如果在包含其头文件时显式声明了库函数,则可以使用下面显示的技术之一来确保声明不是受这样的宏影响。函数的任何宏定义都可以通过将函数名称括在括号中来局部抑制,因为该名称后面没有左括号,表示宏函数名称的扩展。出于相同的句法原因,即使库函数也被定义为宏,也允许获取库函数的地址。使用#undef 删除任何宏定义也将确保引用实际函数。作为宏实现的库函数的任何调用都应扩展为对每个参数只计算一次的代码,并在必要时由括号完全保护,因此使用任意表达式作为参数通常是安全的。同样,以下子条款中描述的那些类似函数的宏可以在表达式中调用具有兼容返回类型的函数可以调用的任何地方。

对于库函数,如宏定义isalpha()必须正常工作了正确的参数,但它不是必需的诊断不正确的参数。由于 C 标准明确允许宏定义,如果实现提供这样的宏,则没有函数调用,因此禁止将char*参数传递给期望 an 的函数的约束int不适用。

如果存在实际的函数调用,则将char*参数传递给需要参数的实际函数int是违反约束的,需要进行诊断。没有从char*到 的隐式转换int

这是一个说明问题的小程序:

#include <ctype.h>
int main(void) {
    char *p = "hello";
    isalpha(p);   // line 4, possible macro invocation
    (isalpha)(p); // line 5, actual function call
#undef isalpha
    isalpha(p);   // line 7, actual function call
}
Run Code Online (Sandbox Code Playgroud)

这是使用gcc -std=c11 -pedantic(gcc 4.8.2)编译的结果:

c.c: In function ‘main’:
c.c:5:5: warning: passing argument 1 of ‘isalpha’ makes integer from pointer without a cast [enabled by default]
     (isalpha)(p); // line 5, actual function call
     ^
In file included from c.c:1:0:
/usr/include/ctype.h:111:1: note: expected ‘int’ but argument is of type ‘char *’
 __exctype (isalpha);
 ^
c.c:7:5: warning: passing argument 1 of ‘isalpha’ makes integer from pointer without a cast [enabled by default]
     isalpha(p);   // line 7, actual function call
     ^
In file included from c.c:1:0:
/usr/include/ctype.h:111:1: note: expected ‘int’ but argument is of type ‘char *’
 __exctype (isalpha);
 ^
Run Code Online (Sandbox Code Playgroud)

在第 5 行,括号isalpha防止宏扩展,暴露函数本身。在第 7 行,由于宏定义已被删除,因此公开了实际函数。

实际的函数调用执行隐式转换,而不是强制转换;由于没有从char*to 的隐式转换int,编译器会发出诊断信息。(它可能会发出致命警告,但 gcc 对隐式转换有些松懈,尽管警告确实满足标准的要求。)使用宏,转换由显式转换运算符执行,编译器不会警告默认。

请注意,根据实现,所有三个调用都可能是实际的函数调用。标准库函数的宏定义是可选的。GNU C 库提供了一个宏定义,isalpha因为宏定义可以比函数调用更有效(尽管内联函数可能同样有效)。