分类目录归档:C语言相关

关于内核ISO C90 forbids mixed declarations and code的警告

昨天有朋友问,如何去掉linux内核编译时出现的“ISO C90 forbids mixed declarations and code”警告。出现这个警告的原因,主要是因为执行了方法之后又出现了变量的定义:

int a = 0;
printk(" a = %d\n", a);
int b = 0;

在C89(90)标准中,不支持这类写法,将b的定义,写到prink之前即可。

这个问题实际是个仁者见仁智者见智的问题,没有绝对的对与错:有人说C89的定义更好,而有人说C99都推出那么多年,标准做这样的改动一定是要更合理了。呵呵,反正老刘也不好说什么,我建议还是遵从内核中大多数开发者的意见,将变量定义写在一起,当某个变量不需要时,删起来也容易不是。这是一个讨论帖:http://www.gossamer-threads.com/lists/linux/kernel/1132941

如果你就喜欢C99的风格,又不想看到警告,那么干脆,满足自己的欲望,直接将kernel下Makefile中的Wdeclaration-after-statement删掉!(老刘可不提倡!)

# warn about C99 declaration after statement
KBUILD_CFLAGS += $(call cc-option, -Wdeclaration-after-statement,)

    类似的问题其实还有个80线的问题,也是备受争议,现在的显示屏幕分辩率这么高,为什么还要坚持80线呢?老刘现在偶尔写写Java代码,用Idea开发环境,它的默认提示线就是120列。看看国外的驱动代码,基本上仍然遵循着80线,但国人写的驱动代码,就多少列的都有了。不过,老刘还是觉得人家的代码更漂亮,所以,老刘写驱动也一直遵循着传统的标准~。

C语言运算符优先级

网上搜罗的C语言y优先级,仔细想想,不管是对于新手和高手,或许都有意义,老刘未找到原作者,只能转载。

 

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

()

圆括号

(表达式)/函数名(形参表)

.

成员选择(对象)

对象.成员名

->

成员选择(指针)

对象指针->成员名

 

2

负号运算符

-表达式

右到左

单目运算符

~

按位取反运算符

~表达式

++

自增运算符

++变量名/变量名++

自减运算符

–变量名/变量名–

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

sizeof

长度运算符

sizeof(表达式)

 

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

表达式-表达式

5

<< 

左移

变量<<表达式

左到右

双目运算符

>> 

右移

变量>>表达式

 

6

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

=

不等于

表达式!= 表达式

 

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

 

13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符

 

14

=

赋值运算符

变量=表达式

右到左

/=

除后赋值

变量/=表达式

*=

乘后赋值

变量*=表达式

%=

取模后赋值

变量%=表达式

+=

加后赋值

变量+=表达式

-=

减后赋值

变量-=表达式

<<=

左移后赋值

变量<<=表达式

>>=

右移后赋值

变量>>=表达式

&=

按位与后赋值

变量&=表达式

^=

按位异或后赋值

变量^=表达式

|=

按位或后赋值

变量|=表达式

 

15

逗号运算符

表达式,表达式,…

左到右

说明:同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

字符串常量

当一个字符串常量出现在表达式中时,它的值是指针常量。编译器把该字符串的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。我们可以对字符串常量进行下标引用、间接访问以及指针运算。

 字符串常量实际上是个指针, “xyz”+ 1这个表达式计算指针值加上 1的值。它的结果也是个指针,指向字符串中的第 二个字符 y

如果使用语句printf("%s", “*xyz"+1); 则输出结果为"yz"

  *“xyz”– 对一个指针执行间接访问操作时,其结果就是指针所指向的内容。字符串常量的类型是指向字符的指针,所 以这个间接访问的结果就是它所指向的字符:x。注意表达式的结果并不是整个字符串,而只是它的第一个字符。

“xyz”[2]

同样可以推断出上面这个表达式的值就是字符 z

在 ANSI C 中,初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串值,程序会出现未定义的行为。在有些编译器中,字符串常量被存放在只允许读取的文本段中,以防止它被修改。

 数组也可以用字符串常量进行初始化:

char a[] = "gooseberry";

与指针相反,由字符串常量初始化的数组是可以修改的。比如下面的语句:

strncpy(a, "black", 5 );

将数组的值修改为blackberry

#include
#include
int main(void)
{
    char *p = "this is a example";
    //char *pi = 3.14; //这样定义是错误的,无法通过编译
    //p[0] = 'T'; //修改该字符串常量时,编译是没问题,但是运行时会出现异常
     
    char a[] = "gooseberry";
    strncpy(a, "black", 5 );
     
    printf("%s\n", p );
    printf("%s\n", a );
    return 0;
}

以上内容摘自《和指针P269。

关于char *p 及数组形式初始化为什么会不同,老刘用objdump了一下,看一下这个,应该就豁然开朗了。

C语言中结构体自引用和相互引用

结构体的自引用(self reference),就是在结构体内部,包含指向自身类型结构体的指针。

结构体的相互引用(mutual reference),就是说在多个结构体中,都包含指向其他结构体的指针。

1. 自引用 结构体

1.1 不使用typedef时

错误的方式:

struct tag_1{  
    struct tag_1 A;   /* 结构体 */  
    int value;  
};

        这种声明是错误的,因为这种声明实际上是一个无限循环,成员b(老刘注:应该是A吧)是一个结构体,b的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。

正确的方式: (使用指针):

struct tag_1{  
    struct tag_1 *A;  /* 指针 */  
    int value;  
};

        由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。

1.2 使用typedef 时

错误的方式:

typedef struct {  
    int value;  
    NODE *link;  /* 虽然也使用指针,但这里的问题是:NODE尚未被定义 */  
} NODE;

这里的目的是使用typedef为结构体创建一个别名NODEP。但是这里是错误的,因为类型名的作用域是从语句的结尾开始,而在结构体内部是不能使用的,因为还没定义。

正确的方式:有三种,差别不大,使用哪种都可以。

/*  方法一  */  
typedef struct tag_1{  
    int value;  
    struct tag_1 *link;    
} NODE;  
  
  
/*  方法二  */  
struct tag_2;  
typedef struct tag_2 NODE;  
struct tag_2{  
    int value;  
    NODE *link;      
};  
  
  
/*  方法三  */  
struct tag_3{  
    int value;  
    struct tag *link;    
};  
typedef struct tag_3 NODE;

2. 相互引用 结构体

错误的方式:

typedef struct tag_a{  
    int value;  
    B *bp;  /* 类型B还没有被定义 */  
} A;  
  
typedef struct tag_b{  
    int value;  
    A *ap;  
} B;

错误的原因和上面一样,这里类型B在定义之前就被使用。

正确的方式:(使用“不完全声明”)

/* 方法一   */   
struct tag_a{  
    struct tag_b *bp;  /* 这里struct tag_b 还没有定义,但编译器可以接受 */  
    int value;  
};  
struct tag_b{  
    struct tag_a *ap;  
    int value;  
};  
typedef struct tag_a A;  
typedef struct tag_b B;   
//老刘注:这里的两个typedef 应该没有什么用处,在两个结构体中没有用到。  
  
/*  方法二   */   
struct tag_a;   /* 使用结构体的不完整声明(incomplete declaration) */  
struct tag_b;  
typedef struct tag_a A;   
typedef struct tag_b B;  
//老刘注:这里的两个typedef 应该没有什么用处,如果这样写,后续的结构体中的"struct tag_a"可以替换成A,"struct tag_b"可以替换成B
struct tag_a{  
    struct tag_b *bp;  /* 这里struct tag_b 还没有定义,但编译器可以接受 */  
    int value;  
};  
struct tag_b{  
    struct tag_a *ap;  
    int value;  
};

转自:http://blog.csdn.net/daheiantian/article/details/6233058

断言 assert(表达式) 相关概念​

本文部分内容来自《C和指P342

断言就是声明某种东西应该为真。ANSI C 现了一个 assert 它在调试程序时很有用。它的原型如下所示:

void assert( int expression );

当它被执行时,这个宏对表达式参数进行测试。如果它的值为假,它就向标准错误打印一条诊断信息并终止程序。 这条信息的格式是由编译器定义的,但它将包含这个表达式所在的源文件的名字以及断言所在的行号。如果表达式为真,它不打印任何东西,程序继续执行。用这种方法使用断言使调试程序变得更容易,因为一旦出现错误, 程序就会停止。而且,这条错误提示信息会准确地提示了症状出现的地点。

这个宏提供了一种方便的方法,对应该为真的东西进行检验。例如:如果一个函数必须使用一个不能为 NULL 的指针参数进行调用,那么函数可以用断言验证这个值:assert( value != NULL );” 如果函数错误的接受 了一个 NULL 数,程序就会打印一条类似下面形式的信息:

Assertion failed: != NULL, file.c line 214

当程序被完整的测试完毕之后,你可以在编译时通过定义 NDEGUG 除所有的断言。你可以使用-DNDEBUG

编译器命令行选项 或者 在源文件中头文件 assert.h 包含之前增加下面这个定义:

#define NDEBUG

当 NDEBUG 定义之后,预处理器将丢弃所有的断言,这样就消除了这方面的开销,而不必从源文件中把所有 的断言实际删除。

#include
#include
//#define NDEBUG
#include
void my_strcopy(char *dest,char *src )
{
    assert(src != NULL);
    assert(dest != NULL);
    assert(strlen(src) >= strlen(dest));
 
    while((*dest++ = *src++) != '\0');
}
 
int main()
{
    char str1[] = "0123456789";
    char str2[] = "abcdefghijk";
 
    printf("原始字符串:\n%s\n%s\n",str1,str2);
    my_strcopy(str1, str2);
    printf("拷贝后的字符串:\n%s\n%s\n",str1,str2);
 
    getchar();
    return 0;
}

define宏的特殊用法

#define宏其实平时编程中用到比较普遍,但有几个使用方法比较特别,所以特地写下来。

1.前加##或后加##,将标记作为一个合法的标识符的一部分,起连接作用。

如:#define A(x) T_##x

则:int A(1) = 10; 等效于 T_1 = 10;

2.前加#@,将标记符转换为相应的字符,仅对单一标记转换有效

如:#define B(x) #@x

则: B(a)'a'

3.前加#,将标记转换为字符串

如:#define C(x) #x

则:C(1+1) "1+1"

4.定义多行宏,最后一行不能用斜杠

#define FUN(this, class)\

{\

 int a = 10;\

}

C语言经典变量定义

用变量a给出下面的定义
a)
一个整型数(An integer
b)
一个指向整型数的指针(A pointer to an integer
c)
一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer 
d)
一个有10个整型数的数组(An array of 10 integers
e)
一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers
f)
一个指向有10个整型数数组的指针(A pointer to an array of 10 integers

g)
一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer
h)
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数(An array of ten pointers to functions that take an integer argument and return an integer 

答案是:
a) int a; // An integer 
b) int *a; // A pointer to an integer 
c) int **a; // A pointer to a pointer to an integer 
d) int a[10]; // An array of 10 integers 
e) int *a[10]; // An array of 10 pointers to integers 
f) int (*a)[10]; // A pointer to an array of 10 integers 
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer 
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer