我写故我在

I write, therefore I am

Posts Tagged ‘C’

悬挂指针(Dangling Pointer)

Posted by ieipi 于 五月 18, 2011

请看下段代码:

    int
main ( int argc, char *argv[] )
{
    unsigned int *pint = new unsigned int; 
    *pint = 5; 
    printf ( "before delete, pint = %p\n", pint );
    delete pint; 
    /*After delete the pointer, we should explicitly set it to NULL */
    /*pint = NULL; */
    printf ( "after delete, pint = %p\n", pint );

    long *plong = new long; 
    printf ( "plong = %p\n", plong );
    *plong = 10; 
    cout << "*pint = " << *pint << "; *plong = " << *plong  << endl; 
    *pint = 20; 
    cout << "*pint = " << *pint << "; *plong = " << *plong  << endl; 
    return EXIT_SUCCESS;
}				/* ----------  end of function main  ---------- */

其输出为:

before delete, pint = 0x8369008
after delete, pint = 0x8369008
plong = 0x8369008
*pint = 10; *plong = 10
*pint = 20; *plong = 20

分析
delete操作只是(让操作系统)解除pint和被分配的动态内存之间的关系.但是这块内存仍在,而且,该指针仍指向这块内存.像这种指向一块已经被释放的内存的指针被称为悬挂指针(dangling pointer).使用悬挂指针容易产生难以调试的运行时错误.
为了减少这种问题,可以在释放一个指针的内存后,强制将它赋值NULL.对NULL地址的操作会立即产生段错误.

Posted in 语言, 技术 | Tagged: , | Leave a Comment »

C 函数声明

Posted by ieipi 于 五月 15, 2011

国内有多少书声称函数必需先声明再调用? 比如<<程序员面试宝典>>P72例7. 那么实际情况呢?
Test 1: 同一个文件

int
main ( int argc, char *argv[] )
{
    double i1, i2;
    /*case 1: no declarations*/
    /*case 2: */
    //int fun1();
    /*case 3: */
    //double fun1();
    /*case 4: */
    //int fun1(int);
    i1 = fun1();
    i2 = fun1(i1);
    printf ( "i1=%d\n", i1);
    printf ( "i2=%d\n", i2);
    return EXIT_SUCCESS;
}				/* ----------  end of function main  ---------- */
fun1(int a, int b)
{
    double d = 3.1234567;
    printf ( "d=%.8f\n", d);
    return d;
}

结果:
case1 和 case2 输出结果相同,均为:

d=3.12345670
d=3.12345670
f1=3.00000000
f2=3.00000000

case3 和 case4 编译错误.
分析:
1).函数名是external object,默认具有全局(global)可见性. 函数原型声明只用于帮助编译器进行类型检查.
2).对于变量,编译器必需进行类型检查,所以必需”先声明后使用”.对于函数可以关闭类型检查(老版C 语言).
3).编译器如果遇到一个事先未声明的标示符,并且紧跟着一个左半括号(即形如”foo(“的语句),就把它默认处理为函数名.并且默认返回类型为int,形参表则不做任何假设.(即默认扩展为函数声明int foo();).
4).对于形参表为空的声明(例如 double atof(); ),编译器将关闭对形参表的类型检查,但是对于返回类型,仍将进行类型检查.
5).一个最简单的函数定义为:

foo() {}

问题:调用此函数的返回结果是多少?
Test 2: 两个文件

/*foo.c*/
foo()
{
    double d = 3.1234567; 
    printf ( "d=%.8f\n", d);
    return d; 
}
/* main.c*/
    int
main ( int argc, char *argv[] )
{
    double f1; 
    /* case 1: no declaration */
    f1 = foo(3, 4); 
    printf ( "f1 = %.8f\n", f1 );

    /* case 2: declaration */
    /*int foo(); */
    /*f1 = foo(); */
    /*printf ( "f1 = %.8f\n", f1 );*/

    /* actually incompatible prototype declaration. */
    /*double foo(int, int); */
    /*f1 = foo(3, 4); */
    /*printf ( "f1 = %.8f\n", f1 );*/

    return EXIT_SUCCESS;
}				/* ----------  end of function main  ---------- */

结果(编译时必需同时编译foo.c和main.c):
case1 和case2 的结果相同

d=3.12345670
f1 = 3.00000000

case3 的结果为:

d=3.12345670
f1 = -nan

分析:
1).编译器的编译检查只能局限在同一个源文件范围类,涉及到多个源文件的情况由连接器解决.
2).链接时只检查函数名,而不检查参数表和返回值(所以c中函数无法重载)
3).注意case3的结果输出为NaN,这是因为foo函数默认返回int型,而该int型的二进制表示恰好不符合浮点数的表示规则.
总结
1.”函数需要先声明后使用”,是一个好的编程规范,但并不是语言特性所要求的.
2.函数声明只用来帮助编译器进行类型检查.若函数定义和声明在同一个文件中,则可以检查定义,声明和调用是否匹配;
若定义和声明不在同一个文件,则只需函数声明和调用保持一致,并且与定义同名.

Posted in 语言, 技术 | Tagged: | Leave a Comment »

C Programming Language (1)

Posted by ieipi 于 四月 21, 2011

好吧,传说中的C语言圣经,我直到现在才看。然知耻而后勇,不亦乐乎!周末抢着看完了,不愧是经典,短小精悍,无以复简。

Chap0: History

本书初版于1978年,再版于1988,时至今日,日久弥新。
C语言由贝尔实验室的Dennis Ritchie于1969~1973年创建。C的名称由来是因为它从早期的B语言中借鉴很多特性。C语言与Unix的诞生相辅相成。1973年,Unix内核用C语言重写,从此一发而不可收拾。1978年,本书第一版诞生,成为C语言的非正式标准文档,被亲切地称为K&R。1983年,ANSI设立了一个专门委员会,致力于建立C标准。1989年,该标准发布,通常被称为ANSI C,或C Standard, 或C89。自此,C语言大致保持不变,1990年代陆续加入了少许新特性,这导致C99的诞生。

Chap1: Tutorial Introduction

Chap2:Types,Operators and Expressions

这些是程序设计语言的基本要素。

2.1 Variable Names

  1. 变量名由字母和数字组成,第一个字符不能是数字。下划线”_”也是字母,但是一般不用做变量的首字母,因为通常库函数会以下划线开头。
  2. 对于internal names,至少前31个字符是有效的;但是对于external names, the standard guarantees uniqueness only for 6 characters and a single case.(???) 。这是因为,外部变量可能会被汇编器和加载器用到,而语言对于这两者是无法控制的。

2.2 Data Types and Sizes

  1. C的语言类型基本为两种:整型和浮点型。前者包括char,short,int,long等,后者包括单精度和双精度。两种都包括有符号和无符号。
  2. C标准对于类型的大小没有明确的规定。编译器只需保证short和int至少16位,long至少32位并且short不大于int,int不大于long。
  3. 浮点类型数据大小同样与硬件平台有关。
  4. 标准库中的和包含相关信息。

2.3 Constants

  1. 字面整常量(literal integer constants, e.g. 1234)为(signed)int。加上后缀U和L后分别可表示unsigned和long。
  2. 字面浮点常量默认为double类型。可加后缀F表示float。
  3. 整形值可用八进制(037=31)或16进制(0x1F=037=31)表示。用这种表示法可以方便看出数值的二进制比特级位表示。
  4. 字符常量本质上是一个整型数值(numerical value)。但是其具体值由机器的character set 决定。采用’0’形式的字符表示,而不直接采用其对应的数值(48 in ASCII)一是因为可读性好,二是与特定字符集无关,增强可移植性。
  5. 某些特殊字符(无法显示)可以用转义序列表示。
  6. 字符串常量:以”/null/0结尾的字符数组。
  7. 枚举常量VS #define ???

2.4 Declarations

  1. 所有的变量在使用前都必须先申明(declared),但是有些声明可以根据上下文隐式地给出(???)。
  2. 初始化:对于external variable和static variable,会默认初始化为0;如果对其显示初始化,则该表达式必须是常量,并且初始化操作只在程序实际运行之前执行一次。 对于automatic variable,应该显性初始化,否则默认值是undefined garbage value。如果对其显性初始化,表达式可以是任意表达式,且初始化操作在程序每次进入该函数或块时都会执行一次。

2.5 Arithmetic Operations

  1. 对于负数,/(截断的方向)和%(余数的符号)运算的结果是与机器相关的。

2.6 Relational and Logical Operators

  1. 对于逻辑操作符&&和||,expressions are evaluated from left to right, and the evaluation stops as soon as the truth or falsehood of the result is known.

2.7 Type Conversions

  1. 当操作数涉及到不同数据类型时,需要进行类型转换。一般而言,低精度到高精度的转换(不会丢失信息) 会自动进行(没有warning?)。高精度到低精度会丢失信息,编译器会给出warning信息,但是不会报错,仍然合法。
  2. 对于char到int的转换,结果有无符号未定,与平台相关。
  3. 基本上算术运算中的隐式类型转换结果满足预期,但是涉及到无符号数时必须小心。这是因为signed与unsigned的比较与机器相关。例如-1L<1u(1u会首先转换成signed>1UL(-1L首先转换成unsigned long).(另据CSAPP2.2.5,如果一个运算数是有符号另一个是无符号,C语言会强制将有符号转换为无符号???)
  4. cast:强制类型转换,它是一个一元操作符,优先级属于第一级(最高一级)。
  5. 函数调用时的类型转换。

2.8 Increment and Decrement Operators

2.9 Bitwise Operators

  1. <<左移位操作的结果是确定的——低位补0;但是右移位>>的结果未定。逻辑右移高位补0,算术右移高位符号扩展。

2.10 Assignment Operators and Expressions

  1. “=”也是一个操作符,其运算结果是其左值执行完赋值操作以后的结果。

2.11 Conditional Expressions

  1. expr1 ? expr2:expr3是条件表达式,和其他表达式效果一样。特别的, int n = 1, float f = 1.0f, (n<0)>

2.12 Precedence and Order of Evaluation

  1. 运算符优先级及结合型如右图。
  2. C语言没有规定同一个运算符的多个操作数的求值顺序。例如x = f() + g()这个表达式中,f和g谁先被调用是不确定的。
  3. 函数的参数求值顺序也是未定的。例如:printf(“%d%d\n”,++n,power(2,n))的结果是未定的。

Chap3: Control Flow

Posted in 语言, 技术 | Tagged: | Leave a Comment »