文档章节

关于c语言等于运算符的一点思考

算法与编程之美
 算法与编程之美
发布于 2013/10/13 13:11
字数 2478
阅读 1592
收藏 12

前言

前些天发表了一篇博文关于c语言内存地址对齐的一点思考引起了大家比较热烈的讨论,的确在这篇文章中示例的选择不是很恰当,示例有很多不严谨的地方,博客的评论中得到了很多同学的指点。也有很多同学指出这种做法技巧性太强,不适合在项目开发中使用,的确是这样,没有深厚的功底尤其是对gcc编译器的深度理解,是比较容易出错。

不过写作该篇文章是为了向大家介绍利用内存对齐的特性来存储一些信息,如果大家今后在别人的代码中看到这种用法,不至于一头雾水,至少记得在osc以前某个时刻某人曾经介绍过这种用法,达到这个目的足以。另外需要注意,思考系列博文都是基于GNU gcc,其他编译器可能会出现不一致的情况。

今天我们要探讨的是c语言中的等于运算符(==),很多同学可能会觉得非常奇怪,这个等于运算符有什么好探讨的呢?无非就是比较两个操作数是否相等,如果相等返回1,否则返回0,如1 == 1表达式返回1,1 == 2表达式返回0,如此简单,有什么可以探讨的呢。

事实真的如此吗?让我们先从一个简单的示例开始吧。

示例

我们的探讨依然是从一个简单的示例开始,如下:

    int a = 10;
    long b = 12;

    (void)(a == b);
    (void)(&a == &b);

代码很简单,定义两个变量a和b,分别为int类型和long类型,第04行代码将表达式(a == b)的值强制转换为void类型,该行代码没有任何作用,加上void的目的在于该表达式不能用于右值,第05行的代码同第04行,区别在于等于运算符的两个操作数由(int,long)变为(int *, long*)。

接下来我们打开编译器所有告警,用gcc -Wall编译一下源程序,看看会出现什么情况。

我们看到编译的结果有点奇怪,第05行代码报告警信息,信息内容如下:

warning: comparison of distinct pointer types

告警信息的意思是不同指针类型的比较。此时,我们感到非常奇怪,第04行和第05行代码没有本质的区别,都是等于运算符表达式,唯一的区别在于操作数的类型不同,04行比较的是(int,long),05行比较的是(int *, long*),所以如果要告警的话,应该这两行代码都会告警才对,因为04行比较的数据类型也不一致。结果为什么只有05行告警,而04行不告警。

分析

当我们遇到这样的一个问题的时候,我们应该如何去分析去探索呢?在此分享一下鄙人的一点浅薄看法。

当遇到这样的问题的时候,毋庸置疑,源码是最好的去处同时也是信息最全的,最不会骗人的地方。我们知道gcc是一个相对比较庞大的项目,要求我们对编译原理有比较扎实的基础。这个时候我们会体会到科班出身和非科班出身的不同之处,也从侧面给目前还正在大学就读的同学一点警示,大学期间不要为了一点小小小的项目经验,就荒废了自己的计算机专业基础,那是非常非常不值得的。

接下来就简单的谈一下分析思路,从抽象到具体,是我一贯的思路。先整体的了解gcc的架构,然后再定位到我们要解决的具体的问题。其中最难的是对问题源码的定位阶段,这里面有两种情况:

1)如果对整体的架构和流程非常熟悉,则可以一步步的分析流程,跟踪到该问题具体的源码;

2)当我们只了解整体的架构,那么我们不可能一下就定位到问题源码,但是我们可以定位到该问题的一个超集,对于本文示例来说,刚开始的时候,不知道问题源码在哪儿,但至少我可以确定该问题肯定是在AST构造阶段。当我们知道这一点后,接下来利用一些技巧,分析该阶段,这样就缩小了我们的问题规模。

这里面介绍一个小技巧,当出现问题的时候,我们如何快速的定位。技巧很简单,就是在gcc源码下直接grep 'warning: comparison of distinct pointer types' -r *,此时可能会有多个地方出现该字符串,再利用我们之前分析得到的问题发生阶段一步步的排除,缩小范围,最终能够定位到我们的源码。

针对本文,定位的源码信息如下(以下只列出与本文分析相关源码,其他省略):

/***
* Notes:
* file location: gcc/c/c-typeck.c
* function: 
    tree
build_binary_op (location_t location, enum tree_code code,
        tree orig_op0, tree orig_op1, int convert_p)
* */

        case EQ_EXPR:
        case NE_EXPR:
...
            if ((code0 == INTEGER_TYPE || code0 == REAL_TYPE
                        || code0 == FIXED_POINT_TYPE || code0 == COMPLEX_TYPE)
                    && (code1 == INTEGER_TYPE || code1 == REAL_TYPE
                        || code1 == FIXED_POINT_TYPE || code1 == COMPLEX_TYPE))
                short_compare = 1;
...
            else if (code0 == POINTER_TYPE && code1 == POINTER_TYPE)
            {
                tree tt0 = TREE_TYPE (type0);
                tree tt1 = TREE_TYPE (type1);
                addr_space_t as0 = TYPE_ADDR_SPACE (tt0);
                addr_space_t as1 = TYPE_ADDR_SPACE (tt1);
                addr_space_t as_common = ADDR_SPACE_GENERIC;

                /* Anything compares with void *.  void * compares with anything.
                   Otherwise, the targets must be compatible
                   and both must be object or both incomplete.  */
                if (comp_target_types (location, type0, type1))
                    result_type = common_pointer_type (type0, type1);
                else if (!addr_space_superset (as0, as1, &as_common))
                {
                    error_at (location, "comparison of pointers to "
                            "disjoint address spaces");
                    return error_mark_node;
                }
                else if (VOID_TYPE_P (tt0))
                {
                    if (pedantic && TREE_CODE (tt1) == FUNCTION_TYPE)
                        pedwarn (location, OPT_Wpedantic, "ISO C forbids "
                                "comparison of %<void *%> with function pointer");
                }
                else if (VOID_TYPE_P (tt1))
                {
                    if (pedantic && TREE_CODE (tt0) == FUNCTION_TYPE)
                        pedwarn (location, OPT_Wpedantic, "ISO C forbids "
                                "comparison of %<void *%> with function pointer");
                }
                else
                    /* Avoid warning about the volatile ObjC EH puts on decls.  */
                    if (!objc_ok)
                        pedwarn (location, 0,
                                "comparison of distinct pointer types lacks a cast");

                if (result_type == NULL_TREE)
                {
                    int qual = ENCODE_QUAL_ADDR_SPACE (as_common);
                    result_type = build_pointer_type
                        (build_qualified_type (void_type_node, qual));
                }
            }

从上面的源码,我们可以看到,当两个操作数都为INTEGER_TYPE的时候(long为long int),只做了一行代码处理:

short_compare = 1;

而当两个操作数都为POINTER_TYPE的时候,则做了很多判断:

comp_target_types (location, type0, type1)

addr_space_superset (as0, as1, &as_common)

VOID_TYPE_P (tt0)

VOID_TYPE_P (tt1)

从上面我们可以看到,第一个comp_target_types函数比较了两个操作数指针指向的的数据类型是否一致,当都不满足上述这些条件的时候,编译器就会打印一条告警信息:

 pedwarn (location, 0,
                                "comparison of distinct pointer types lacks a cast");

从上面我们可以看到gcc在处理等于运算符EQ_EXPR的时候,不同的操作数类型其处理逻辑是不一样的。上述源码很好的解释了我们示例中提出的问题。

注1:int和long对于我们来说可能认为是两种数据类型,但对于gcc来说,都是INTEGER_TYPE;

注2:后续有时间再详细的和大家一起探索下gcc的架构;

应用

从以上分析,我们知道二元运算符等于运算符,当两个操作数都为指针类型的时候,编译器会先进行指针所指向的数据类型的检查等诸多操作之后再比较操作数的值。

在了解了gcc编译器的这个特性之后,我们自然会思考,这个特性有什么用途呢?接下来我们就看一下关于该特性的一个简单应用。

#define max(x,y) ({ \
     typeof(x) _x = (x);›\
     typeof(y) _y = (y);›\
     (void) (&_x == &_y);›   \
     _x > _y ? _x : _y; })

这是一个计算两个数最大值的宏,先获得x,y变量值,然后就使用了我们的等于运算符的特性。我们知道宏和函数最大的区别在于宏不能做静态数据类型检查而仅仅是简单的替换,所以一旦出现错误,就很难定位。

第04行的代码的作用就是比较x和y的数据类型是否一致,当类型不一致的时候,就会编译器告警,这样做就避免了计算不同数据类型的最大值。通过这种方式,使得我们在使用宏的同时,可以让我们的代码更加健壮。

从上面的应用我们知道,当我们需要检查两个变量的数据类型是否一致的时候,就可以利用==运算符在处理两个操作数都是指针类型的情况,会对指针指向的数据类型进行比较。

总结

本文先从一个简单的示例引出了gcc在计算等于运算符表达式,当两个操作数都为指针类型的时候的不同处理方式。再从gcc源码的角度分析了,为什么会出现这种情况。接着介绍了该特性的一个简单应用。从以上的分析,我们知道,当我们越来越了解我们的编译器的时候,我们就可以编写更加高效,更加健壮的代码。

这里再吐槽一下,与其花时间去做一些没有太大意义的事情如研究c++强大的功能和语法等,不如花更多的时间去了解我们所使用的系统(这里系统包括编译器,os,处理器等),编写高效的代码,因为我们实在是太不了解我们的系统,有太多的东西值得我们去思考去探索。

引用

【1】http://en.wikibooks.org/wiki/GNU_C_Compiler_Internals/GNU_C_Compiler_Architecture

【2】http://www.airs.com/dnovillo/200711-GCC-Internals/200711-GCC-Internals-1-condensed.pdf

【3】http://www.linux.org/


如果您想了解更多细节问题或分享交流更多技术,欢迎添加微信公众号:

                                           


© 著作权归作者所有

共有 人打赏支持
算法与编程之美
粉丝 290
博文 91
码字总数 100037
作品 0
成都
程序员
加载中

评论(2)

算法与编程之美
算法与编程之美

引用来自“DISSECTOR”的评论

《水煮gcc》卖书的节奏。。。
不同类型的指针比较,推荐看此例:
http://stackoverflow.com/questions/6702161/pointer-comparisons-in-c-are-they-signed-or-unsigned

好的,谢谢。
DISSECTOR
DISSECTOR
《水煮gcc》卖书的节奏。。。
不同类型的指针比较,推荐看此例:
http://stackoverflow.com/questions/6702161/pointer-comparisons-in-c-are-they-signed-or-unsigned
python3基础知识讲解(二)

引言 生活中有太多的不容易了,既然来到了这个世界,那么就请在坚持一下,就那么一下,你会挺过去的;没有人事一番风顺的,只有经得起磨练的人才会成功的,所以,坚持,在坚持!记住一句话:...

长风留言
05/30
0
0
案例深入浅出分析C语言中“i++”与"++i”的区别

C语言学习是一个长期的过程,坚持固然能成功。但是一味的看知识点,不去思考就无法进步了,现在我的打算就是写一点深入浅出的文章,就不做知识的搬运了,没意思。今天我们就来探讨一下C语言“...

诸葛玥
05/31
0
0
关于c语言结构体成员变量访问方式的一点思考

前言 上篇博文(关于c语言结构体偏移的一点思考)对c语言中结构体偏移做了一些思考,发现博文中还有一些小的问题,没有描述的足够清楚,所以才萌生了本篇博文的想法。 为什么不直接将本篇博文作...

算法与编程之美
2013/06/27
0
11
《C陷阱与缺陷》之1词法"陷阱"

编译器中负责将程序分解为一个一个符号的部分,一般称为"词法分析器"。在C语言中,符号之间的空白(包括空格符、制表符或换行符)将被忽略。 1、=不同于== C语言使用符号"="作为赋值运算符,符...

肖邦0526
2015/11/09
0
0
如何"引用"传递参数从一个函数中得到多个返回值

通过“引用”传递参数的方法从一个函数中得到多个返回值。就像三元运算符一样,大部分受过正式编程训练的程序员都知道这个技巧。但是那些 HTML 背景大于 Pascal 背景的程序员都或多或少的有过...

PPP
2012/03/25
0
3

没有更多内容

加载失败,请刷新页面

加载更多

崩溃bug日志总结1

目录介绍 1.1 java.lang.UnsatisfiedLinkError找不到so库异常 1.2 java.lang.IllegalStateException非法状态异常 1.3 android.content.res.Resources$NotFoundException 1.4 java.lang.Ille......

潇湘剑雨
57分钟前
0
0
学习大数据为什么要先学Java?

计算机编程语言有很多,目前用的多一点的就是Java,C++,Python等等。目前大多数学习大数据的人都是选择学习Java,那Java到底好在哪呢?为什么学大数据之前要先学Java呢?我们今天就来分析一...

董黎明
今天
1
0
php删除服务器所有session

php删除服务器所有session踢掉所有在线用户linux 注意:如果要删除服务器上所有session,重启php服务是解决不了问题的,php的session是持久化的。 有效解决办法: 删除 /tmp 下的所有文件(默...

妖尾巴
今天
0
0
Ubuntu18.04 安装最新版WPS

1.手动卸载libreoffice:sudo apt-get remove --purge libreoffice* 2.官网下载WPS和字体: WPS:http://wps-community.org/download.html 字体:http://wps-community.org/download.html?vl......

AI_SKI
今天
4
0
数据结构(算法)-图(深度优先搜索 DFS)

#include <iostream>using namespace std;#define MaxVex 30typedef char VertexType;typedef struct vexNode adjList[MaxVex];struct edgeNode{int adjvex;//邻接点......

ashuo
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部