最丑陋的C语言特性:tgmath.h

C 语言_系统函数_tgmath.h深度解析 C 语言_系统函数tgmath.h库深度解析 阅读详情

<tgmath.h>是一个在C99引入的,标准C语言库提供的头文件。对于Fortran编写的数值软件,它向C语言提供更加简洁的接口。

跟C语言不同,Fortran提供了编写在该语言内部的“固有函数”,其表现得更像操作符一样。固有函数接受不同类型的参数,并根据参数的类型返回对应类型的返回值。同时,Fortran中的普通函数(“外部函数”)的行为跟C语言中的函数类似,对类型要求严格(即函数参数的类型必须符合,返回值也是固定的)。举个例子,Fortran77提供了一个名为INT的函数,它能够接受Integer、Real、Double和Complex的参数,并总是返回Integer。另有一个名为SIN的函数,接受Real、Double和Complex的参数并返回相同类型的值。这两个函数仅仅是固有函数的一小部分。

某种意义上,这个特性帮了程序员不少忙,因为即使变量类型改变了,函数调用也不需要更改。另一方面,用户定义的函数不能像这样工作,因此这些附加的便利性只有在不调用用户定义函数的情况下才成立。

仅仅根据以上描述,就已经有一些C程序员认为这个特性是丑陋的了。同样的理由,他们认为把printf整合到C中一样丑陋。

这个功能和其他特性在C99被整合进C语言,包含在在之前提到的中,目的是更好的支持数值计算。其中提供了三角函数和对数函数,舍入相关的函数和少数其它函数。这个头文件定义了一系列宏,覆盖了中已有的一些函数;例如,cos宏在参数是double的时候表现得像cos函数一样,参数是float时像cosf,参数是long double时像cosl,double _Complex时像ccos,参数是float _Complex时像ccosf,参数是long double _Complex时像ccosl。最终,如果参数是任何整形,宏调用cos函数,就像参数被隐式的转换为了double类型一样。

这个特性丑陋的第二个理由在于它试图模仿成函数,但是这个模仿不但不完美,甚至是非常危险的:如果你尝试着将泛型宏cos当成一个参数传递给函数,而事实上它总是被当做对应double的cos函数,因为cos后面不紧跟一个左括号的话宏根本不会展开。

最后一个被认为丑陋的理由在于,这样的宏在严格意义上的C上根本不能实现,它们需要依靠某种编译器支持——另外,某些经验(例如,glibc实现中bug被发现的速度)表明,这个特性基本上没有使用过,因此不应该被算作这个语言核心的一部分,尤其是它根本就不支持潜在的特性。(相比之下,<stdarg.h>对便携性的支持就非常的好。)

说了这么多,这个特性又丑陋有没有实用价值,我干嘛提到它?我写这个文章的原因是我在考察glibc的时候,发现它是一个如此天才的实现。我认为它应该用一种更好的办法被后人铭记,而不是像下面这样的注释一样。

2000-08-01 Ulrich Drepper drepper@redhat.com

Joseph S. Myers jsm28@cam.ac.hk

* math/tgmah.h: Make standard compliant. Don’t ask how.

最直接模仿Fortran编译器的方法是使用一个简单的宏:(我会用cos来举大部分例子,其他宏的语法是相似的。)

1
#define cos(X) __tgmath_cos(X)

编译器会将__tgmath_cos当做内部操作符,然后将其转换成某一个前端的函数调用。

我见过的被推选出的最简洁的解决方法,是在编译器前段给基本函数加上了重载支持,这可以利用运营商扩展来实现。(否则,C语言标准会要求编译器检查某个标示符的不兼容声明。)

1
2
3
4
5
6
7
8
9
#define cos(X) __tgmath_cos(X)
#praga compiler_vendor overload __tgmath_cos
double __tgmath_cos ( double x)
{ return ( cos ) (x); }
float __tgmath_cos ( float x)
{ return cosf (x); }
long double __tgmath_cos ( long double x)
{ return cosl (x); }
...

(简单的习题: 为什么在定义__tgmath_cos(double)时,cos两旁有括号呢?)

当然,仅仅为了<tgmath.h>的这个目的而实现它是一件非常繁杂的工作。(虽然它有可能能在C++前端上工作。)没人想在C语言中用这样一个笨重的扩展,何况本就没多少程序使用<tgmath.h>,所以似乎这样扩展编译器有些不值得。

glibc的实现必须依靠那些用已经成熟的gcc版本推出的扩展,因此要实现它更加复杂了。

首先,让我们实现一个选择正确函数类型的宏吧。因为C语言不支持条件宏扩展,因此条件判断语句需要包含在扩展代码中。我们需要像下面这样代码:

1
2
3
4
5
6
7
8
9
10
11
12
#define cos(X) \
   (X is real) ? ( \
     (X has type double \
       || X has an integer type) \
       ? ( cos ) (X) \
       : (X has type long double ) \
       ? cosl(X) \
       : cosf (X) \
   ) : (
     (X has type double _Complex) \
     ? ccos (X)
....

而且,我们发现写上面那样的条件判断语句非常简单。

  • “x is real”就是sizeof (X) == sizeof (__real__ (X))
  • “x has an integer type”就是(typeof (X))1.1 == 1(中等的习题:(__typeof__ (X))0.1 == 0不正确。这是为什么呢?) (事实上,glibc在某些情况使用了__builtin_classify_type,一种嵌入式的内部gcc,而在上述情况使用了另一种相似的替代。)
  • “x has type double/long double/float“也能被sizeof区分。但在有些硬件结构下,一些C类型被映射成相同的硬件类型,这时区分的结果可能那么精确,不过在这些硬件结构下这些不同类型的运算都没有差别,而且外部的C语言也不能识别出差别了。就C语言的”as-if”原则来说,这算是相当不错的了。

好的,这样一来我们的cos宏就能选择正确的函数来调用了。不过不幸的是,它总是返回long double _Complex类型的结果。原因在于,? :操作符的返回值的类型会是第二和第三操作数类型的“常用算术转换”。

我们能够避免这些类型转换来使用我们自己选择的类型,这需要另一个gcc扩展,声明表达式:

1
2
3
4
5
6
7
8
9
#define cos(X) ({ result_type __var; \
   if (X is real) { \
     if ((X has type double ) \
       || (X has an integer type)) { \
       __var = ( cos ) (X); \
     else if (X has type long double ) \
       __var = cosl (X); \
...
   __var; })

现在,这个宏的结果永远会是result_type,问题引刃而解。

是吗?

事实上并没有。我们该怎么定义result_type?对于浮点数类型我们可以直接用__typeof__ (X),但我们又想用double作为整形参数,况且C语言并没有一种对于类型的? :操作数,是吧。

前两个练习放在那儿,并不是因为我是个老师,想检查一下你的进度。它们是为了最后最有难度的习题准备的——或者是为了在你到这里之前就把你吓跑。(好吧,我想我已经把大家都无聊死了,没人能读到这儿了。)虽然这个习题的上下文提示的已经够多了,也可能仍然不足以解答,来看看吧:

困难的习题:以下两个结果有何不同?

1
1 ? ( int *)0 : ( void *)0

和

1
1 ? ( int *)0 : ( void *)1

以及为什么?

不像之前的两个习题稍作研究和思考就能解决,这个习题(尤其是为什么的部分)有可能要求你阅读C语言标准,因此我在这里做出解释。

首先,解释一下概念是必要的:

  • 从编译器的角度来说,一个整形常数表达式就是一个整形表达式有一个常数值:编译器能够计算这个常数而不用任何除了常数合并以外的优化。尤其是这个表达式不会用到任何其他变量的值。
  • 空指针就是一个值等于整数值0的指针。空指针能够是任何类型的指针。
  • 空指针常量是一种句法结构。空指针常量的值在转换成一个指针类型时,是一个空指针(“空指针”和“它的值”都在上文说过了)。空宏展开成空指针。

因为空指针常量是一种句法结构,它就有一个句法定义,它要不是一个等于零的整型常量,要不一个转换成void *的表达式。举个例子,0, 0L, 1 - 1, (((1 - 1))), (void *)和(void *)(1 - 1)都是空指针常量,但(int *)0和(void *)1就不是。

(其实,当其定义为一个表达式的值时,它就不是一个句法结构了。不过最好就这样假装它是个句法结构,因为大部分情况下,“值为零的整型常量表达式”其实就是字面上的0。)

现在我们来看看C语言标准的6.5.15部分的第六段,这部分讲到了条件操作符? :,有以下内容:

如果第二和第三操作数都是指针…,那么结果类型也会是一个指针…。更有,如果两个操作数都是指向类型相兼容的指针的话…,结果类型会是一个…指向其合成类型的指针;如果一个操作数是一个空指针常量,结果类型跟另一个操作数的类型相同;否则,…结果类型是一个指向void…的指针。

因此,在下面表达式中

1
1 ? ( int *)0 : ( void *)0

第三个操作数是一个空指针常量,因此结果是(int *)0。而在

1
1 ? ( int *)0 : ( void *)1

中,第三个操作数不是一个空指针常量,因此结果是。这就是我们对于类型的条件操作符,我们只需再稍加修缮。

注意到这个表达式(其中X是个整形)是一个整形常量表达式。

1
1 ? (__typeof__ (X) *)0 : ( void *)(X has a integer type)

因此,如果X是一个整形变量,结果就是(void *)0,否则就是。而下面这个式子。

1
1 ? ( int *)0 : ( void *)(!(X has an integer type))

在X是整形的情况下结果是(int *)0,否则结果是(void *)0。注意到两个情况中都有其中一个结果是(void *)0。

我们定义上面两个表达式分别为E1和E2,那么,以下表达式:

1
1 ? (__typeof__ (E1))0 : (__typeof__ (E2))0

在x是整形的时候为(int *)0,否则为(__typeof__ (X) *)0。同上,我们注意到有一个表达式总是空指针常量。

最后,我们定义result_type为:

1
__typeof__ (*(1 ? (__typeof__ (E1))0 : (__typeof__ (E2))0))

这就对了。对于多于一个参数的宏来说会稍微复杂一点,不过基本概念和方法都和上面描述的一样。


原文链接: Miloslav Trmač   翻译: 伯乐在线 - Hacker_YHJ
译文链接: http://blog.jobbole.com/49139/

C++ Reference: Standard C++ Library reference: C Library: ctgmath 阅读详情

相关推荐

使用 AFL++ 对 IoT 二进制文件进行模糊测试 - 第一部分

American fuzzy lop 是一款面向安全的模糊测试器,它采用一种新型的编译时插桩和遗传算法,可以自动发现干净、有趣的测试用例,从而触发目标二进制文件中新的内部状态。这显著提高了模糊测试代码的功能覆盖率。AFL 的地址是它已经有一段时间没有更新了。虽然 AFL 仍然运行良好,但有一个新项目 AFL++,它是 AFL 的一个分支,包含许多改进和新功能。AFL++ 的地址是,其源代码位于。在本文中,我们将研究如何使用 AFL++ 对 IoT 二进制文件进行模糊测试。

技术超强的网络安全平台 1088

C语言(Tgmath.h库(C99),exit和atexit)

Tgmath.h库(C99),exit和atexit

little_startoo的博客 770

SAP ABAP开发避坑:BAPI_MATVAL_PRICE_CHANGE调用报‘估价未维护’的完整解决流程

本文深入解析SAP ABAP开发中调用BAPI_MATVAL_PRICE_CHANGE函数时遇到的'估价未维护'错误,提供从基础配置检查到CKML_PRICE_SEND表维护的完整解决方案。通过实战案例和代码示例,帮助开发者快速定位问题并实现批量价格更新的自动化处理,提升开发效率。

weixin_28705087的博客 427

C库——数学库和断言库

C标准规定了一些预定义宏C99新增inline关键字时,它是唯一的函数说明符(关键字extern和static是存储类别说明符,可应用于数据对象和函数)。C11新增了第2个函数说明符——Noreturn,表明调用完成后函数不返回主调函数。exit()函数是_Noreturn函数的一个示例,一旦调用exit,它不会再返回主调函数。size_t类型被定义为sizeof运算符的返回值类型——无符号整数类型,通常是unsigned int 或unsigned long。

2303_80078385的博客 1357

基于YOLOv5的隧道衬砌缺陷智能检测系统

计算机视觉在工程检测领域正发挥越来越重要的作用,特别是目标检测技术通过深度学习算法实现了对复杂场景的智能分析。YOLOv5作为当前主流的目标检测框架,通过改进网络结构和训练策略,可以显著提升检测精度和效率。在隧道工程领域,结合通道注意力机制和多尺度特征融合技术,能够有效解决衬砌缺陷识别中的小目标检测难题。这种AI检测系统相比传统人工判读地质雷达图像,不仅将效率提升20倍,还能将识别准确率提高到92.3%,特别适用于3cm以上结构缺陷的自动化检测。系统通过深度可分离卷积等优化,实现了模型轻量化,便于在边缘计算

weixin_29064103的博客 326

多普勒效应及多普勒频移的简单推导

多普勒频移的简单推导

m0_47867419的博客 3万+

C标准库源码解剖(15):数学函数库math.h, tgmath.h和complex.h

    数学库是一个庞大的库,而且数学函数的实现一般要涉及到特有的数值算法,因此这里也是作一个概述性的介绍。标准C中的数学库有3个头文件math.h, tgmath.h和complex.h,其中tgmath.h和complex.h是C99中引入的。标准C中定义的数学函数主要是初等函数,包括绝对值函数、高斯函数(即求最近整数)、指数函数、对数函数、幂函数、三角函数与反三角函数、双曲函数与反双曲函数等

Jack Zhou的专栏 1万+

ST03N 里看不到 SE38 / SA38?别急,这不是没记录,而是被换了名字

摘要:SAP系统中SE38/SA38事务码在ST03N统计中可能"隐身",因其对话步骤被归集到对应的程序名(如SE38→RSABAPPROGRAM)。ST03N侧重工作负载聚合统计,而STAD提供短期明细记录。正确排查方法是:在ST03N中搜索程序名而非事务码,再通过STAD验证具体使用情况。这一现象在权限整改和审计中易造成误判,建议将程序名纳入常规监控,并区分ST03N(趋势分析)与STAD(事实校验)的不同用途。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 953

关于ios的<tgmath.h> (转载的)

是一个在C99引入的,标准C语言库提供的头文件。对于Fortran编写的数值软件,它向C语言提供更加简洁的接口。 固有函数接受不同类型的参数,并根据参数的类型返回对应类型的返回值。同时,Fortran中的普通函数(“外部函数”)的行为跟C语言中的函数类似,对类型要求严格(即函数参数的类型必须符合,返回值也是固定的)。举个例子,Fortran77提供了一个名为INT的函数,它能够接受I

chaoxiaofan的博客 924

YOLOv5/v7/v8/v9/v10/v11/v12/13目标检测结果解析,超w字精读,IoU、GIoU、DIoU、CIoU、EIoU详细解释,精确率、召回率、AP、mAP与P-R 曲线图深度解析

YOLOv5/v7/v8/v9/v10/v11/v12/13目标检测结果解析,超w字精读,IoU、GIoU、DIoU、CIoU、EIoU详细解释,精确率、召回率、AP、mAP与P-R 曲线图深度解析

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 1413

【AUTOSAR-COM】-2.3_通过PduGroup批量控制CAN Frame/Pdu的接收

-->如果实现类似UDS诊断 $28服务,控制所有APP CAN Rx Frame/Pdu/报文接收; -->自定义过滤CAN信号的接收。

「汽车电子助手」的博客 1991

C语言-第16章 C预处理器和C库

C预处理器和C库

m0_52729352的博客 521

Underactuated Robotics - 欠驱动机器人学(一)- 全驱动与欠驱动系统

根据牛顿的观点,机械系统的动力学是二阶的(其状态由位置向量 q(也称为配置向量)和速度向量以及(可能还有)时间给出。二阶控制动力系统的一般形式是:其中 u 是控制向量。定义 1.1 (欠驱动控制微分方程) 二阶控制微分方程描述如下:在状态和时间 t 中,如果所得到的映射是可射的:对于每一个,都存在一个能产生预期响应的,那么系统就是全驱动的。否则,就是欠驱动(状态在时间 t)。

机器人控制与仿真应用 3377

tgmath.h

NAMEtgmath.h - type-generic macrosSYNOPSIS#include <tgmath.h>DESCRIPTIONThe <tgmath.h> header shall incude the headers <math.h> and <complex.h> and shall define seve...

weixin_34186128的博客 336

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

改善一个劣质的C++代码

在C++论坛里发了一个与本文题目相同的贴子如下:下面是一个我在一个应用系统中发现的我认为很丑陋的一段C++代码.显然,代码的意图是将string中的内容变成相应的小写字母.请大家指出它的问题之所在,并给出改善后的代码.我自已已经有了一个答案,只用一条语句就能实现这个功能.待两日后我给出我的答案.但然,也许你的答案比我的更好. void IniReader::string_lower

海涛的专栏 1457

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

0x01基础中的进阶——深度解析各种算法竞赛中常用的编程小技巧(1.01持续更新)

先上一段代码 Warning:部分功能DevC++不支持,请使用对GNU c++17有较好支持的编译器 #include "bits/stdc++.h" //#pragma GCC optimize ("O3") //#pragma GCC target ("sse4") using namespace std; #define DEBUG typedef long long ll; typedef long double ld; typedef complex<ld> cd; t

u012681544的博客 907

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

math.h, tgmath.h和complex.h

 数学库

2247

Visual Studio 2013支持的C99库(library)

 C99已经发布多年,但微软的Visual C++尚未完整支持C99。我偶然看到VS官方博客上一篇介绍C99库支持的文章,特翻译了一下,希望对大家有帮助。水平有限,翻译不当之处,欢迎指正。 大家好,我是Pat Brenner,Visual C++库团队的开发人员。在这篇博文中,我想分享添加到Visual Studio 2013中的 C运行时库(run-time library)对C99

polar9527的专栏 4198

编程算法 - 数丑陋 代码(C)

数丑陋 代码(C)本文地址: http://blog.csdn.net/caroline_wendy题目: 我们把仅仅包括因子2, 3 和 5的数称作丑数. 求按从小到大的顺序的第5个丑数.能够设置一个数组包括所须要的丑数, 依次比較乘以2, 乘以3, 乘以5的最小的数, 最后返回结果.如第5个丑数是5, 如1, 2, 3, 4(2*2), 5均是丑数.代码:/* * main.cpp *...

weixin_34216036的博客 163

可恶的C语言——代码风格

代码风格 #include指令应置于整个程序最上方。 缩进应使用4个空格或等长的Tab,且应可区分代码内外层的逻辑。 右花括号前不应该有多余空行。 独立意义的代码块应用空行隔开,但不应该有两个连续的空行。 尽量少使用全局变量。且局部变量不应该与上一代码块中变量重名,但可与全局变量重名。 只在一个函数中使用到的需全局空间或生命周期的变量,必须定义为static的局部变量。 空函数可使用不换行的{}。 void类型的函数常规返回应省return。 非void类型的函数不应无默认情况的return。 命名应使用

blll0的博客 262

c语言:把只含因子2、3和5的数称为丑数,求按从小到大的顺序的第1500个丑数(两种方法比较)...

把只含因子2、3和5的数称为丑数,求按从小到大的顺序的第1500个丑数。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上把1当作第1个丑数。算法1:逐个判断每个整数是不是丑数的解法,直观但不够高效#include<stdio.h>int ugly(int number){ while (number % 2 == 0) { number /= 2; } ...

weixin_33826609的博客 750

Linux下C库学习 - math.h

math.h众所周知,如果你想使用一些数学函数,那么就需要包含math.h,这里面基本包含了所有常用的数学函数(sin,cos,tan),以及数学常量(PI),等等,既然C库里面开发好了,那我们就看看到底里面都有什么。既然是数学函数库,首先我们就需要对操作数进行限制,比如0不能作为除数,对负数开方,对负数求对数等等,这些在数学上本身就是不合理的,那如果让计算机去处理这些计算,很可能就会引起错误,因此

mill_li的博客 8186

我的ubuntu12.04下的 /usr/include 头文件

root@Ubuntu32:/home/zhangbin# tar jcvf usr_include.tar /usr/include -C /home/zhangbin/codeStore/ ahplayer/ android2_VLC4AndroidMeCompile_Scripts.full.tar.gz androidVLC/ EclipseWork/ git_vlc/ GSt

突围 5820
上一篇: C\C++代码优化的27个建议
下一篇: C++11 中的线程、锁和条件变量
普通网友
博客等级 码龄14年 9粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值