C语言中容易被忽略的细节(第三篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节。我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?……我坚信,好记性不如烂笔头。第三篇了,fight~...

第一篇链接:C语言中容易被忽略的细节(第一篇)

第二篇链接:C语言中容易被忽略的细节(第二篇)

1、__attribute__((noreturn))

__attribute__可设置函数属性、变量属性和类型属性。__attribute__((noreturn))设置了函数属性,noreturn通知编译器函数从不返回值,当遇到函数需要返回值却还没运行到返回值处就已退出来的情况,该属性可以避免出现错误信息。例如:

void Test();
int main(void)
{
    int x;
    scanf("%d", &x);

    if (x > 0)
        Test();
    else
        return 0;
}

上面代码在编译阶段提示警告:warning: control reaches end of non-voidfunction。产生警告的原因是:Test()函数没有返回值,所以main()函数有可能没有返回值,程序不知如何处理。

void Test() __attribute__((noreturn));

若将Test()的声明修改为以上,则相当于通知编译器Test()函数从不返回值,也就不会产生警告了。

注意:(1)attribute前后的下划线输入的方法是:Shift+减号(输入两次);(2)__attribute__机制有很多其他用处,是GNU C的特色,建议读到相关内容时候再总结,做到有的放矢。

2、如果编译器按照内存地址递减的方式来给变量分配内存,以下代码有什么问题?

int i, a[10];
    for (i = 0; i <= 10; i++)
        a[i] = 0;

解析:数组越界,程序陷入死循环。内存中数组a之后的四个字节实际上分配给了整形变量i,对a[10]赋值为0实际上是将计数器i的值设置为0。

3、在if/else结构中,要尽量把为TRUE的概率较高的条件判断置于前面,这样可以提高该段程序的性能。switch的效率比if/else结构高,即使程序真的不需要default处理,也应保留语句default:break;

4、与零值比较的正确方法

(1)布尔变量与零值比较

if语句判断其条件表达式的真假并不是通过把它的计算结果转换为布尔类型的临时变量进行的,而是将其结果直接和0进行比较,如果不等于0则表示真,否则为假。不要将布尔变量直接与true、1、-1、0等进行比较。

bool flag;
if (flag)  //表示flag为真
if (!flag) //表示flag为假  

(2)整型变量与零值比较

int value;
if (value == 0)
if (value != 0)  

(3)浮点变量与零值比较

计算机表示浮点数(float或double)都有一个精度限制。对于超过了精度限制的浮点数,计算机会把它们精度之外的小数部分截断。因此,本来不相等的两个浮点数在计算机中可能就变成相等的了。例如:float a = 10.222222225, b = 10.222222229;在数学上a和b是不等的,但在32位计算机中它们就是相等的。(注:float可保证6位有效数字,double和long double可保证10位有效数字)在针对实际应用环境编程时,总是有个精度要求,而直接比较一个浮点数和另外一个值(浮点数或整数)是否相等(==)或不等(!=)可能得不到符合实际需要的结果,因为==和!=比较操作采用的精度往往比实际应用中要求的精度高。

可将“>”和“<”直接用于浮点数之间比较及浮点数和整数的比较。!(a > b) && !(a < b)与a == b的语义是等价的,所以也不建议用于判断浮点数相等与否。

#define EPSILON 1e-6
if (abs(x - y) <= EPSILON) //x等于y
if (abs(x - y) > EPSILON)  //x不等于y  

if (abs(x) <= EPSILON)      //x等于0
if (abs(x) > EPSILON)       //x不等于0  

(4)指针变量与零值比较

指针变量的零值是“空值”(记为NULL),即不指向任何对象。尽管NULL的值与0相同,但两者意义不同(类型不同)。

if (p == NULL)
if (p != NULL) 

备注:使用if (NULL == p)、if (100 == i)这种写法比较好,因为如果误将==写为=,因为编译器不允许对常量赋值,就可以检查到错误。

5、va_list、va_start()、va_arg()和va_end()

C标准函数库的stdarg.h头文件定义了可变参数函数使用的宏。可变参数函数内部必须定义一个va_list变量,然后使用宏va_start、va_arg和va_end来读取。相关定义如下:

typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap ); 

举个简单例子如下:

#include <stdarg.h>
#include <stdio.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count);                //使va_list指向起始的参数
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double);        //检索参数,必须按需要指定类型
    va_end(ap);                         //释放va_list
    return tot/count;
}

int main(void)
{
    double a[5] = {1, 0, 0, 0, 0};
    printf("%lf\n", average(5, a[0], a[1], a[2], a[3], a[4]));
    return 0;
}

C语言中可变参数函数在没有长度检查和类型检查,在传入过少的参数或不符的类型时可能会出现溢位的情况。

6、求值顺序

C语言中只有4个运算符(&&,||,? :和, )存在规定的求值顺序。&&和||首先对左侧操作数求值,只在需要时才对右侧操作数求值;对于a ? b : c,操作数a首先被求值,根据a的值再求操作数b或c的值;对于逗号运算符,先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值。例如:

i = 0;
while (i < n)
    y[i] = x[i++];

以上从数组x中复制前n个元素到数组y中的代码是不正确的。因为赋值运算符并不保证任何求值顺序。y[i]的地址将在i的自增操作执行之前被求值还是之后求值是不确定的。

注意:运算符的优先级与求值顺序是完全不同的概念。

7、对于数组结尾之后的下一个元素,取它的地址是合法的,读取这个元素的值的结果是未定义的,而且绝少有C编译器能够检测出这个错误。

8、字符数组不一定是字符串

字符数组是元素为字符变量的数组,字符串是以’\0’(ASCII码值为0x00)为结束字符的字符数组。

(1)对于字符数组来说,它并不在乎中间或末尾有没有’\0’结束字符,因为数组知道它自己有多少个元素,况且’\0’对它来说是一个合法的元素。

(2)如果字符数组中没有’\0’结束标志,却被当做字符串来用时可能会导致“内存访问冲突”或篡改其他内存单元,strlen函数的结果异常等。举个简单的例子:

#include <stdio.h>

int main(void)
{
    char arr1[] = {'a', 'b', '\0', 'c', 'd'};
    char arr2[] = "Hello";
    char *p = "Hello";

    printf("%d %d\n", sizeof(arr1), strlen(arr1));      //结果5 2
    printf("%d %d\n", sizeof(arr2), strlen(arr2));      //结果6 5
    printf("%d %d\n", sizeof(p), strlen(p));            //结果4 5
    return 0;
}

9、不要用字面常量来初始化引用

const int &a = 0;

以上语义并非是把引用初始化为NULL,而是创建一个临时的int对象并用0来初始化它,然后再用它来初始化引用a,而该临时对象将一直保留到a销毁的时候才会销毁。

10、引用的创建和销毁并不会调用类的构造函数和析构函数。在二进制层面,引用一般是通过指针来实现的,只不过编译器帮我们完成了转换。

时间: 2024-08-08 22:07:33

C语言中容易被忽略的细节(第三篇)的相关文章

C语言中容易被忽略的细节(第二篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节.我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?--我坚信,好记性不如烂笔头. 第一篇链接:C语言中容易被忽略的细节(第一篇) 1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来.C语言中数组元素可以是任何对象,也可以是另外一个数组,即数组的数组. 2.C语言允许初始化列表出现多余的逗号.例如:int days[] = {1,2,3,};作用:方便自动化生成代码.

C语言中容易被忽略的细节(第一篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节.我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?--我坚信,好记性不如烂笔头. 1. 在C语言中,符号之间的空白(包括空格符.制表符或换行符)将被忽略.但一个符号的中间不能有空白,否则可能被解释为另一个或几个符号.以下两种写法是等价的: //写法1 if (x > big) big = x; //写法2 if ( x > big ) big = x ; 2.编译器将程序分解为符号的&q

C语言中容易被忽略的细节(第四篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节.我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?--我坚信,好记性不如烂笔头.第四篇了,fight~... 第一篇链接:C语言中容易被忽略的细节(第一篇) 第二篇链接:C语言中容易被忽略的细节(第二篇) 第三篇链接:C语言中容易被忽略的细节(第三篇) 1.void*类型的指针不能参与算术运算,只能进行赋值.比较和sizeof操作的原因? 指针的算术运算还要包含指针所指对象的字节数信息.

程序猿之---C语言细节26(C语言中布尔类型、continue细节、sizeof举例、strlen举例)

主要内容:C语言中布尔类型.continue细节.sizeof举例.strlen举例 一.布尔类型 可能很多人不知道现在C语言已经有了布尔类型:从C99标准开始,类型名字为"_Bool" 在C99标准之前我们常常自己模仿定义布尔类型,常见有以下几种方式: 1.方式一 #define TURE 1 #define FALSE 0 2.方式二 typedef enum {false, true} bool; 3.方式三 typedef int bool 闲int浪费内存,对内存敏感的程序使

Java中JNI的使用详解第三篇:JNIEnv类型中方法的使用

转自: http://blog.csdn.net/jiangwei0910410003/article/details/17466369 上一篇说道JNIEnv中的方法的用法,这一篇我们就来通过例子来看一下这些方法的使用: 首先是第一个例子:在Java代码中定义一个属性,然后再C++代码中将其设置成另外的值,并且输出来 先来看一下Java代码: [java] view plain copy package com.jni.demo; public class JNIDemo { public i

Go 语言中数据类型的判断

Go 语言中数据类型的判断,本文介绍三种方法.方法一:使用i.(type)结合空接口(interface{}) func main() { v1 := "中国你好" v2 := 20 var v3 byte = 65 fmt.Printf("v1的数据类型为:%s\n", checkType(v1)) fmt.Printf("v2的数据类型为:%s\n", checkType(v2)) fmt.Printf("v3的数据类型为:%s\n

[老老实实学WCF] 第三篇 在IIS中寄存服务

原文:[老老实实学WCF] 第三篇 在IIS中寄存服务 老老实实学WCF 第三篇 在IIS中寄宿服务 通过前两篇的学习,我们了解了如何搭建一个最简单的WCF通信模型,包括定义和实现服务协定.配置服务.寄宿服务.通过添加服务引用的方式配置客户端并访问服务.我们对WCF的编程生命周期有了一个最基本的了解. 在前两篇中演示的例子,一定要力求背着做下来,包括源程序.配置文件都要背着一行行的手写下来,这样才能有深刻的体会.WCF的知识零散复杂,必须扎扎实实的学习和练习.如果你还没有做到了然于胸,现在赶紧翻

C语言中一些乱七八糟的用法与细节(不断更新)

用C语言比较多,这篇是平时攒下的.有些内容在工作后可能会很常见,但是不用容易忘,所以就写篇博客吧. 一.printf的用法 %*可以用来跳过字符,可以用于未知缩进.像下面一样. for(i = 1; i < 10; i++) { printf("%*c\r%*c\n",  9 - abs(i - 5), '*', abs(i - 5) + 1, '*'); } %[]可以用来读取指定的内容,%[^]可以用来忽略指定内容(正则表达式?) %m可以不带参数,输出产生的错误信息 二.关

Java中容易被你忽略的细节(一)

1.在一个程序当中代码段访问了同一个对象从单独的并发的线程当中,那么这个代码段叫"临界区" 怎么解决呢:使用同步的机制对临界区进行保护 同步的两种方式:同步块和同步方法 对于同步来说都是使用synchronized方法 每一个对象都有一个监视器,或者叫做锁. java用监视器机制实现了进程之间的异步执行 2.Struts框架基于MVC模式 Struts的工作流程: 在web应用启动时就会加载初始化ActionServlet,ActionServlet从 struts-config.xm