`

C语法总结 底层设计和声明

 
阅读更多

 

位运算符

使用底层技术进行一些位操作,可以编写加密,视频程序,以及需要高速执行或高效利用空间的程序非常有用。但是过度依赖底层操作会导致程序丧失可移植性。如果一定要使用尽量将操作限定在特定的模块中而不要分散在各处。

符号 含义
<< 左移位
>> 右移位
~ 按位求反
& 按位与
^ 按位异或
| 按位或

为了可移植性,最好仅对无符号数进行移位运算

优先级来说,依次是 ~ ,&, ^, |

//这些操作符不会改变原有变量的值,所以需要如下    
i <<= 2;    
k = i & j;    
    
unsigned short i , j, k;    
i = 21;            // i is       21 ( 0000 0000 0001 0101 )    
j = 56;           // j is        56 ( 0000 0000 0011 1000 )    
k = ~i;           // k is  65514( 1111 1111 1110 1010 )    
k = i & j;        // k is         16( 0000 0000 0001 0000 )    
k = i ^ j;        // k is         45( 0000 0000 0010 1101 )    
k = i | j;         // k is          61( 0000 0000 0011 1101 )   
  
  
//设置i 的第n位置,惯用写法,假设要设置的位置存放在j中  
i |= 1<<j;       //如果j是3,则1<<j为0x0008  
  
//要清楚第n位置  
i &= ~(1<<j);     //如果j是3,则1<<j是0x0008,取反就是0XFFF7,再  
                          //跟i 进行与运算  
  
//要测试第n位置是否被设置了  
if( i & 1<<j );  
  
//用位运算访问位域  
//修改位域,将二进制值101存入变量i 的第4至第6位  
i = i & ~0x0070 | 0x0050;  
//假设j 包含了需要存到到i 的第4位至第6位的值,因为 | 优先级低括号可以去掉  
i = (i & ~0x0070) | (j<<4);  
  
//获取位域,获取变量i 的0至2位  
j = i & 0x0007;  
//如果位域不在i 的右端,需要先移位在提取,如果要获取i 的4到6位  
j = (i >> 4) & 0x0007;  

 

 

 

结构中的位域

struct file_date {
    unsigned int day : 5;
    unsigned int month : 4;
    unsigned int year : 7;
};
//这里的day占5个比特,month占4个比特,year占7个比特

为了提高可移植性:将所有的位域声明为unsigned int 或signed int

 

 

 

其他底层技术

//定义占一个字节的类型
typedef unsigned char BYTE;

//占2个字节的类型
typedef unsigned short WORD;

//模拟x86的cpu寄存器
union {
    struct {
        WORD ax, bx, cx, dx;
    } word;
    struct {
        BYTE al, ah, bl, bh, cl, ch, dl, dh;
    } byte;
} regs;
regs.byte.ah = 0x12;
regs.byte.al = 0x34;
printf("AX= %hx\n",regs.word.ax);    //AX = 1234

当存储的数据多于一个字节时,有两种存储方式,大头派和小头派

x86的CPU是小头派,也就是高位后存储,0x1234在内存中就是34 12

大头派如IBM的CPU或者是java虚拟机,是先存储高位再存储低位,0x1234在内存中的位置就是12 34

//可以打印出当前程序的地址
printf("main address = %x\n", (unsigned int)main);

//volatile 跟java中的volatile类似,表示当前的内存是易变的,即每次读取时都会
//重新向内存中读取一次,而不会读取寄存器中的值
while(缓冲区未满) {
    //等待输入
    buffer[i] = *p;
    if(buffer[i++] == '\n') {
        break;
    }
}

//有的编译器会检查到p和 *p都没有改变,于是对程序进行优化,变成:
在寄存器中存储*p
while(缓冲区未满) {
    //等待输入
    buffer[i] = 存储在寄存器中的值;
    if(buffer[i++] == '\n') {
        break;
    }
}
//将p声明为volatile可以避免编译器做优化

 

 

 

声明语法

声明的语法规则

存储类型    类型限定符    类型说明符    声明符(也就是变量)

存储类型包括:    auto,static,extern,register

类型限定符包括:    const,volatile,restrict(C99语法,只能限定指针)

类型说明符包括:    void,char,short, int,long, float,double,single, unsigned

                        类型说明符的出现顺序是无关的int unsigned long 和 long unsigned int 完全一样

                        struct {int x, y} 或者typedef创建的类型名也是类型说明符

                        C99中还有函数说明符inline,只能用于函数

//存储类型    类型限定符      类型说明符               声明符
extern          const         unsigned long int     a[10];

 

 

 

存储类型

C程序中每个变量都具有以下3个性质:

1.存储期限  变量存储期限决定了为变量预留和内存被释放的时间。

性质 说明
存储期限

变量存储期限决定了为变量预留和内存被释放的时间

自动存储期限 的变量在所属块被执行时获得内存单元,并在块终止时释放内存单元,从而

导致变量失去值

静态存储期限 的变量在程序原型期间占有同一个存储单元,也就是允许无限期地保留它

的值

作用域

变量的作用域是指可以引用变量的那部分程序文本

块作用域      变量从声明的地方一直到所在的末尾都是可见的

文件作用域   变量从声明的地方一直到所在文件的末尾都是可见

链接

变量的链接确定了程序的不同部分可以共享此变量的范围

外部链接  变量可以被程序中的几个(或许全部)文件共享

内部链接  变量只能属于单独一个文件,但此文件中的函数可以共享这个变量

无链接      变量属于单独一个函数而且根本不能被共享

//静态存储期限, 文件作用域,外部链接
int i;

//自动存储期限,块作用域, 无链接
void f(void) {
    int j;
}

  

 

auto存储类型

只对属于块的变量有效,auto变量具有自动存储期限,块作用域,无链接。auto存储类型几乎从来不用明确

的指明,因为对于在块内声明的变量,它是默认的。

 

static存储类型

可以用于全部变量,而无需考虑变量的声明位置,当变量在块外部时static说明变量具有内部链接;当变量在块内时候static把变量存储期限从自动变量静态

//静态存储期限, 文件作用域, 内部链接
static int i;

//静态存储期限, 块作用域, 无链接
void f(void) {
    satic int j;
}

把块外部的变量i 声明为static时,文件内部的函数都可以访问这个变量但是其他文件中的函数不可以,static的此种用法可以用来实现一种称为信息隐藏的技术

static变量具有以下一些性质:

1.块内的static变量只在程序执行前进行一次初始化,而auto变量则会在每次出现时进行初始化

2.每次函数被递归调用时,它都会获得一组新的auto变量,但是如果函数含有static变量,那么此函数的全部

   调用都可以共享这个static变量

3.虽然函数不应该返回指向auto变量的指针,但是函数返回指向static变量的指针是没有错误的

 

extern存储类型

可以使几个源文件共享一个变量,这种共享方式和共享函数很类似,把函数定义在一个源文件中,然后在需要调用此函数的其他文件中放置声明,共享外部变量的方式和此方式非常类似

//这样的声明不会给变量i 分配存储单元,只是提示编译器需要访问定义在其他地
//方的变量(可能稍后在同一个文件中,但更常见是在另一个文件中)
//extern可以用于所有类型的变量,包括数组,声明数组时可以省略数组长度
extern int i;

//对变量进行初始化的extern声明是变量的定义
extern int i = 0;
//等价于
int i =0;

//extern声明中的变量始终具有静态存储期限,变量的作用域依赖于声明的位置,
//如果声明在块内部,那么变量具有块作用域;否则变量具有文件作用域

//静态存储期限,文件作用域, 什么链接?
extern int i;

//静态存储期限,块作用域,什么链接?
void f(void) {
    extern int j;
}

确定extern类型变量的链接有一定难度。如果变量在文件中较早的位置(任何函数定义的外部)声明为static,那么它具有内部链接;否则(通常情况下)变量具有外部链接。

 

register存储类型

这种存储类型要求编译器把变量存储在寄存器中,而不是像其他变量一样保留在内存中

register变量具有和auto变量一样的存储期限(自动),作用域(块作用域),链接(无链接)

但是寄存器没有地址,所以对register变量做 &操作,也就是取地址操作是非法的。

 

函数的存储类型

函数的存储类型只有extern和static

在函数的声明处定义extern说明函数具有外部链接,也就是允许其他文件调用此函数;

声明为static说明此函数是内部链接只有当前文件才可以调用。

如果不加说明则函数具有外部链接

//外部链接
extern int f( int i );

//内部链接
static int g( int i );

//外部链接
int h( int i );

 

建议使用static存储类型,这样的好处是:

1.更容易维护,对此函数内部的修改不会影响其他函数,也容易定位问题

2.减少了"名字空间污染",声明static函数具有内部链接,所以其他文件中可以重新使用这个函数的名字

   带有外部链接的大量函数名可能导致C程序员所说的"名字空间污染",即不同文件中的名字意外地发生了

   冲突,使用static存储类型可以有效解决这个问题。

int a;
extern int b;
static int c;

void fun(int d, register int s) {
    auto int g;
    int h;
    static int i;
    extern int j;
    register int k;
}

上面定义的变量(注意没有函数f )

名字 存储期限 作用域 链接
a 静态 文件 外部
b 静态 文件 不确定
c 静态 文件 内部
d 自动
e 自动
g 自动
h 自动
i 静态
j 静态 不确定
k 自动

 

 

 

类型限定符

包括 const,volatile,和C99中的restrict(只能用于指针)

const用于声明变量是只读的

const int n = 10;

把对象声明为const有以下几种好处:

1.const是文档格式,声明对象是const类型可以提示任何阅读程序的人,该对象的值不会改变

2.编译器可以检查程序没有特意的视图改变该对象的值

3.当为特定类型的应用(如嵌入式)编写程序时,编译器可以用单词const来识别需要存储到ROM(只读存储器)

   中的数据

 

const和#define的区别

1.可以用#define指令为数值,字符或字符串创建名字。const可用于产生任何类型的只读对象,包括数组,

   指针,结构和联合

2.const对象遵循与遍历相同的作用域规则,而用#define创建的常量不受这些规则的限制,特别是不能用

   #define创建具有作用域的常量

3.和宏的值不同,const对象的值可以在调试器中看到

4.不同于宏,const对象不可以用于常量表达式

5.对const对象应用取地址运算符& 是合法的,因为他有地址,宏没有地址

没有绝对的原则说明何时使用#define以及何时使用const。这里建议对表示数字或字符的常量使用#define。

这样就看以把这些常量作为数组维数,并且在switch语句或其他要求常量表达式的地方使用它们

 

volatile修饰这个变量为可变的,就是每次去读这个变量的时候,都不会从寄存器中读取,而直接去读内容,以防止编译器做优化

 

restrict 这个关键字只能用于指针,表示之后指针指向的内容都必须由这个指针还修改,而不能通过其他途径修改

 

//表示s1和s2的地址不能重叠,否则函数可能不能执行
void * memcpy(void * restrict s1, const void * restrict s2, size_t n);

//s1和s2的地址可以重叠
void * memove(void * s1, const void * s2, size_t n);

 

 

 

声明符

无论声明有多么复杂,都有下面两条规则来解释:

1.始终从内往外读声明符,换句话说,定位声明的标示符,并且从此处开始解释声明。

2.在做选择时,始终使[]和()优先于* ,如果* 在标示符的前面,而标示符后边跟着(),那么标示符表示函数而不是指针。

 

//[]优先级比* 高,所以ap是数组指针
int *ap[10];

//fp是标识符,由于*在标识符前面,且后边跟着(),()优先级更高,所以fp是返回
//指针的函数
float *fp(float);

//pf的 类型 1.指针指向,2是具有int类型的实际参数函数,3.返回void类型值
void (*pf) (int);

//某个特定的声明是不合法的
//函数不能返回数组
int f(int)[];

//函数不能返回函数
int g(int)(int);

//函数类型的数组也是不合法的
int a[10](int);

//一个复杂的声明
int *(*x[10])(void);
//首先x[10]表明是一个数组,然后*x[10]表示一个指针指向的数组,
//void说明是不带有实际参数的函数,最后int 表示返回的是一个指向int类型的
//指针



具有自动存储期限 的变量没有默认初始值

 

具有静态存储期限 的变量默认初始值为0

所以静态存储期限的指针变量默认就是空指针,而自动存储期限的指针默认值可能是任意内容

但是为了阅读和代码风格,所有的变量包括(静态存储期限)都应该提供显示的初始化。

 

 

 

内联函数

C99中的新关键字 inline。这个关键字只能作用于函数

如果一个函数被调用了上亿次,那么定义为inline,可以让编译器更好的优化,编译成机器代码增加执行速度。

inline类似于register和restrict关键字,都是用于提升性能的。

 

inline double average(double a, double b) {
    return (a + b)/2;
}
average具有外部链接,但是编译器并没有考虑average的定义是外部定义(它是内联定义)所以视图在别的文件中调用average将被认为是错误的。

 

1.可以将函数改成内部链接

static inline double average(double a, double b) {
    return (a + b)/2;
}

2.或者将函数放到一个头文件中

 

C99中规定如果特定文件中某个函数的所有顶层声明中都有inline都没有extern,则该函数定义在该文件中是内联的。如果在程序的其他地方使用该函数(包含其内联定义的文件也算),则需要在另一个文件中为其提供外部定义。

 

C99中对具有外部链接的内联函数(未对具有内部链接的内联函数做约束)做了如下限制:

1.函数中不能定义可改变的static变量

2.函数中不能引用具有内部链接的变量

这样该函数可以定义同时为static和const变量,但是每个内联定义都需要分别创建该变量的副本。

 

gcc仅当使用 -O 命令行选项请求进行优化时,才会对函数进行“内联”。

 

分享到:
评论

相关推荐

    C语言全套资料 C语言程序设计 C语言算法 C语言课件 C语言顺序程序设计,C语言数组,C语言循环控制,C语言预处理命令,C语言文件操作指针,C语言选择结构程序设计,C语言结构体与共用体,C语言文件操作,C语言函数

    它包括基本语法,如变量声明、数据类型、运算符、流程控制(条件语句和循环语句)等。 2. **C语言算法**:算法是解决问题的步骤或方法,C语言中的算法学习涉及到排序算法(如冒泡排序、快速排序)、查找算法(如...

    编译原理语法分析C语言实现

    总的来说,这个项目提供了一个实践编译原理中语法分析的实例,通过C语言和递归子程序法,可以帮助学习者深入理解编译器工作原理,并提升他们在语言处理方面的技能。通过分析和改进这个实现,开发者可以进一步探索...

    编译原理 语法分析 语法树生成

    总结来说,编译原理的语法分析部分涉及到使用yacc和lex构建解析器,以生成C++源代码的抽象语法树。这个过程不仅对于编写编译器或解释器至关重要,而且对于提升程序员对语言内部运作机制的理解也非常有帮助。通过实践...

    c语言版语法分析器

    本项目提供了一个C语言实现的语法分析器,特别适用于编译原理的课程设计,帮助学生深入理解和实践编译器的工作原理。 **1. C语言基础** C语言是一种强大的、低级别的编程语言,广泛应用于系统编程、软件开发、...

    C语言常用语法

    本文将深入探讨C语言中的指针使用,以及命名空间、extern和static等关键语法概念。 首先,我们来看指针,它是C语言的灵魂之一。指针是存储内存地址的变量,通过指针可以间接访问和修改其他变量的值。理解指针的使用...

    Rust 实现的 C 语言词法分析器和语法分析器.zip

    下面我们将深入探讨Rust实现C语言词法分析器和语法分析器的相关知识点。 首先,让我们了解词法分析器和语法分析器的基本概念: 1. **词法分析**:词法分析器负责读取源代码,将其分解为一系列有意义的单元,称为...

    pl0语法分析器

    PL/0是一种简单的编程语言,由Pascal语言的创造者Niklaus Wirth设计,用于教学和演示编译原理。PL/0语法分析器是理解编译器构造基础的一个重要工具,它帮助学习者理解如何将源代码转换为机器可执行的形式。在本篇...

    C语言程序设计(程序设计与算法)

    《C语言程序设计》这本书是学习C语言编程的宝贵资源,尤其对于初学者而言,它提供了全面的基础知识和语法规则。C语言是一种强大的、低级的编程语言,被广泛应用于系统开发、嵌入式系统、游戏引擎以及各种软件应用...

    Yacc程序实现语法树功能C编译器

    总的来说,本项目通过Yacc和Lex实现了一个C编译器,它能够识别并处理C语言的基本操作、函数和数组等特性,并构建语法树以进行深入的语义分析和优化。这对于理解和学习编译原理、解析技术以及C语言的底层机制具有很高...

    自制c语言编译器

    在编译器设计中,我们需要理解C语言的所有语法规则,包括变量声明、数据类型、运算符、流程控制语句(如if-else、switch-case、for、while)、函数定义与调用、指针操作等,这些都是编译器进行源代码解析的基础。...

    自己总结的c&c++语法知识点

    本总结主要关注在实际开发过程中易错和易混淆的语法点,旨在帮助开发者提高代码质量,避免常见陷阱。 1. **类型转换**: - 静态类型转换(`static_cast`)用于强制转换,但不能检查安全性。 - 动态类型转换(`...

    C语言经典《C程序设计语言》(电子版)

    1. **基本语法与数据类型**:C语言的基础始于它的语法结构,包括变量声明、常量定义、运算符和表达式。书中详细介绍了各种数据类型,如整型(int)、字符型(char)、浮点型(float和double),以及如何通过类型转换在它们...

    C语言 课件 c ppt

    通过学习C语言,学生不仅可以掌握一种编程语言,还能建立起对计算机底层工作原理的基本认识,为后续学习更高级的编程语言和技术打下坚实的基础。在实际工作中,C语言广泛应用于操作系统、嵌入式系统、设备驱动、游戏...

    Mini C的词法和语法分析程序

    在这个主题中,我们聚焦于一个特殊的项目——“Mini C的词法和语法分析程序”,这是一个简化版的C语言解析器,用于教授基本的编译原理和实践。 首先,我们来看词法分析。词法分析器,也被称为扫描器或词法生成器,...

    C语言设计,c程序设计语言第二版,官方题解

    《C语言设计,c程序设计语言第二版,官方题解》是C语言学习的重要参考资料,主要针对丹尼斯·里奇和布莱恩·科尼格所著的《C程序设计语言》(第二版)进行深入解析和实践指导。这本书通常被程序员、计算机科学学生...

    C语言课程设计 歌星大奖赛程序.zip

    首先,我们需要理解C语言的基础语法,包括变量声明、数据类型、运算符、控制结构(如if语句和循环)、函数定义与调用等。这些是编写任何C程序的基础。在设计这个歌星大奖赛程序时,可能会涉及结构体来存储歌手信息,...

    C语言程序设计现代方法第2版全部课后习题参考答案.pdf

    《C语言程序设计现代方法第2版》是一本深入学习C语言的经典教材,其全面覆盖了C语言的基础概念、语法和高级特性。书中的499道课后习题是检验学习成果和提升编程能力的重要途径。这些习题涵盖了从基本的数据类型、...

    C语言五子棋 课程设计

    开发者需要掌握C语言的基本语法,如变量声明、条件语句、循环结构以及函数定义等。 五子棋游戏的实现涉及到了数据结构的设计,比如使用二维数组来表示棋盘状态,每个元素代表一个棋位,0表示空白,1表示黑棋,2表示...

    编译原理_java语法分析器_C语言版 源码

    总之,“编译原理_java语法分析器_C语言版 源码”是一个深入理解编译原理、Java语法和C语言编程的实践项目。通过研究和理解这个项目,学习者可以在编程语言的底层机制上有更深入的认识,提高自己的编程技能和解决...

    C语言程序基础设计教程

    总的来说,这个教程提供了一个全面的C语言学习框架,从语言的历史、特点到基本的语法结构和编程实践,旨在帮助新手快速上手并深入理解C语言的精髓。通过实际操作和验证教程中的例子,学习者将能够逐步掌握C语言编程...

Global site tag (gtag.js) - Google Analytics