linux开发之C语言

1、预编译

1.1 预处理

问题1:什么是预编译?何时需要预编译?
预编译又称预处理,程序执行前的一些预处理工作。主要处理#开头的指令。如拷贝#include包含的文件代码、替换#define定义的宏、条件编译#if等。.
何时需要预编译:
1、总是使用不经常改动的大型代码体。
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头

1.2、用宏编写2数取最小值

#define MIN(x, y) {((x)<(y)?(x):(y))} //宏定义中函数体最好加{},防止变量值未释放

1.3、#与##的作用

#是把宏参数转化为字符串的运算符,##是把两个宏参数连接的运算符。

#define STR(arg) #arg          则宏STR(hello)展开时为”hello”
#define NAME(y) name_y      则宏NAME(1)展开时仍为name_y
#define NAME(y) name_##y    则宏NAME(1)展开为name_1
#define DECLARE(name, type) type name##_##type##_type 
则宏DECLARE(val, int)展开为int val_int_int

1.4、如何避免头文件被重复包含

用#ifndef方式或者#pragma once(C++11以上编译器)二者区别详解

2、关键字

2.1、static

static局部变量,放置在全局区,类似全局变量
static全局变量、函数只能在声明它的源文件中使用,不能被其他文件通过extern引用

static类成员变量:
1)static成员在定义时分配存储空间,不能在类声明中定义;静态数据成员不属于任何对象;(单例模式)
2)遵从public, protected, private访问规则;<数据类型><类名>::<静态数据成员名>=<值>
3)两种访问方式:<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
与全局变量区别:
1)static成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性
2)可以实现信息隐藏。static成员可以是private成员,而全局变量不能

static类成员函数:
1)静态成员函数没有this指针,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数
2)出现在类体外的函数定义不能指定关键字static
3)非静态成员函数可以任意地访问静态成员函数和静态数据成员

2.2、const

1)预编译指令只是对值进行简单的替换,不能进行类型检查
2)可以保护被修饰的东西,防止意外修改,增强程序的健壮性
3)编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
4)常量指针、指针常量、指向常量的常指针

const int * n; //不能通过这个指针改变变量的值,但是还是可以通过其他的引用来改变变量的值的,常量指针可以指向其他的地址
 
int const * n;//指指针本身是个常量,不能在指向其他的地址,是地址中保存的数值是可以改变的,可以通过其他指向改地址的指针来修改

const int* const p; //以上两种情况集合,但是依然可以通过其他的普通指针改变变量的值

5)修饰函数的参数

void StringCopy(char *strDestination, const char *strSource);//防止修改指针指向的内容
void swap ( int * const p1 , int * const p2 )//防止修改指针指向的地址
//以上两种情况结合

6)修饰函数的返回值

const char *str = GetString();
//以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被//赋给加const 修饰的同类型指针

2.2.1、const与define区别

1)const定义的常数带类型,define不带类型,
2)const是在 编译、运行的时候起作用,而define是在编译的预处理阶段起作用。
3)define只是简单的字符串替换,没有类型检查
4)define预处理后占用代码空间,而const占用数据段空间。
5) const常量可以进行调试的,define是不能进行调试的,主要是预编译阶段就已经替换掉了,调试的时候就没它了。
6)const不能重定义,不可以定义两个一样的,而define就比较牛气了,它通过undef取消某个符号的定义,再重新定义。
7) define可以用来防止头文件重复引用,而const不能
8)使用define会使得代码看起来非常简单,而const无法实现该功能

2.2.2、const与非const转换

const_cast:主要是用来去掉const属性,或者加上const属性(C++)

去掉const属性:const_case<int*> (&num)
加上const属性:const int* k = const_case<const int*>(j)

2.3、volatile

变量如果加了 volatile 修饰,则会从内存重新装载内容,而不是直接从寄存器拷贝内容
应用场合:在中断服务程序和cpu相关寄存器的定义
1)中断服务程序中修改的供其它程序检测的变量需要加volatile;
2)多任务环境下各任务间共享的标志应该加volatile
3)存储器映射的硬件寄存器通常也要加voliate

2.3.1、常见问题

1)一个参数既可以是const还可以是volatile?
可以,例如只读的寄存器
2)一个指针可以是volatile ?
可以,例如当一个中断服务子程序修改一个指向一个buffer的指针时
3)以下代码问题

int square(volatile int *ptr) 
{ 
    return *ptr * *ptr; 
} 
等同于如下:
int square(volatile int *ptr)  
{ 
    int a,b; 
    a = *ptr; 
    b = *ptr; 
    return a * b; 
} 
//*ptr的值可能被意想不到地该变,因此a和b可能是不同的
//正确代码如下:
long square(volatile int *ptr)  
{ 
    int a; 
    a = *ptr; 
    return a * a; 
}

2.4、extern

此变量/函数是在别处定义的,要在此处引用
1). extern修饰变量的声明或者函数
2)可用于指示C或者C++函数的调用规范

extern "C"
{
    double sqrt(double);
    int min(int, int);
}
extern "C"
{
    #include <cmath>
}

3) 不可以将extern"C" 添加在函数内部
4)如果函数有多个声明,可以都加extern"C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
4)除extern"C", 还有extern “FORTRAN” 等

extern和static

1)extern和static不能同时修饰一个变量;
2)static修饰的全局变量声明与定义同时进行;
3)static修饰全局变量的作用域只能是本身的编译单元

extern和const

C++中const修饰的全局常只能作用于本编译模块中,可以与extern连用来声明该常量可以作用于其他编译模块中

2.5、sizeof

sizeof是在编译阶段处理,且不能被编译为机器码。sizeof的结果等于对象或类型所占的内存字节数。sizeof的返回值类型为size_t

2.5.1、与strlen对比

1)strlen 是一个函数,它用来计算指定字符串 str 的长度
2)sizeof 是一个单目运算符,参数可以是数组、指针、类型、对象、函数等

2.6、malloc和free

申请动态内存和释放内存
底层实现原理

2.6.1、对比new/delete

new和delete实现原理

1)new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

2)使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

3)new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

4)new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

5)new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

6)C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。

7)new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分3配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

2.6.2、malloc(0)返回值

如果请求的长度为0,则标准C语言函数malloc返回一个null指针或不能用于访问对象的非null指针,该指针能被free安全使用,所以使用malloc返回的指针需要判断指针是否为空

3、结构体

自定义类型
1)结构体之间可以嵌套
2)结构体是一种数据类型,不能存放数据,不占用空间,结构体变量能存放数据需要储存空间。
3)结构体成员在内存中的储存情况也是按顺序的

3.1、大小计算

需要符合字节对齐原则:
1)结构体变量的首地址,必须是结构体变量中的“最大基本数据类型成员所占字节数”的整数倍。
2)结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节数的整数倍。
3)结构体变量的总大小,为结构体变量中“最大基本数据类型成员所占字节数"的整数倍
在这里插入图片描述
注意:结构体数组成员的大小为0,不占用结构体的内存,可以在结构体中分配不定长的大小;

typedef struct st
{
        inta;
int b;
char c[0];//不占用结构体的内存
}st_t;
//sizeof(st_t)等于8,即char c[0]的大小为0.
#define SIZE 100
st_t *s = (st_t *)malloc(sizeof(st_t) + SIZE);

3.2、对比联合体

联合体:对同一内存空间采取不同类型进行解释,联合体变量的长度时成员最长的长度,联合体变量赋值时,新的值会冲掉旧的值;

3.3、对比类

结构体能够实现类说实现的功能
最本质的一个区别就是默认的访问控制: struct是public的,class是private的

3.4、位域

struct st1
{
unsigned chara:7;/*字段a占用了一个字节的7个bit*/
unsigned charb:2;/*字段b占用了2个bit*/
unsigned charc:7;/*字段c占用了7个bit*/
}s1

好处:
1)有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。这样节省存储空间,而且处理简便。
2)可以很方便的利用位域把一个变量给按位分解
缺点:
不同系统对位域的处理可能有不同的结果,所以位域的使用不利于程序的可移植性

4、内存模型

堆、栈、代码区、全局数据区
在这里插入图片描述

4.1、全局区

1)初始化的全局变量和静态变量在一块区域,
2)未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。(BSS段)
3)常量数据存放在另一个区域里。
这些数据在程序结束后由系统释放

4.2、栈

在函数中定义的变量存放的内存区域。由系统自动分配与释放,不需要程序员考虑资源回收的问题,方便简洁。
地址分配是从内存的高地址开始向低地址分配

4.3、堆

通过指令自主向系统申请的内存区域,大小由自己决定,它在使用完后同样需要自己通过指令去释放该区域内存,否则将有可能出现内存的浪费与溢出。释放需要注意指针初始化,防止野指针。

4.4、代码区

用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。

5、函数

5.1、函数参数

详细原理参考
C语言函数参数入栈顺序是从右向左的,这是由编译器决定的,更具体的说是函数调用约定决定了参数的入栈顺序。C语言采用是函数调用约定是__cdecl的,所以对于函数的声明,完整的形式是:int __cdecl func(int a, int b);
传参方式:
值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量
注:在C语言中,值传递是唯一可用的参数传递机制,指针也是值传递,只不过是传递的是指针的值

1)进程的堆栈存储区是主调函数和被调函数进行通信的主要区域
2)被调函数使用的堆栈区域结构为:
    局部变量(如temp)
    返回地址
    函数参数
    低地址
    高地址
3)由主调函数在调用后清理堆栈
4)函数的返回值一般是放在寄存器中的(为了支持中断)

C++中的引用传递:申明需要在要使用"&"符号,而调用时则不用

5.2、内联函数

有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗,引入内联函数避免了频繁调用函数对栈内存重复开辟所带来的消耗;
关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用

void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
{
}

劣势:
1)只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)
2)内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。(空间换时间)

6、野指针

指针变量中的值是非法内存地址,进而形成野指针

6.1、产生

1)、局部指针变量没有初始化
2)、指针所指向的变量在指针之前被销毁
3)、使用已经释放过的指针
4)、进行了错误指针运算
5)、进行了错误的强制类型转换

6.2、避免

1)、绝不返回局部变量和局部数组的地址
2)、任何变量在定义后必须0初始化
3)、字符数组必须确认0结束符后才能成为字符串
4)、任何使用与内存操作相关的函数必须指定长度信息

6.3、常见情况

1)、指针未初始化
2)、指针未分配足够的内存
3)、内存分配成功,但并未初始化
4)、内存操作越界

6.4、内存操作

1)动态申请内存后,检查值是否为NULL,防止使用NULL值
2)free后的指针,必须置NULL
3)内存操作必须带长度
4)malloc和free必须匹配

7、可变参数列表

函数参数的个数或者类型在编写函数时未确定的情况, C语言允许使用 可变参数列表, 用 , … 来表示.
注意点:
1)含有可变形参的形参列表中, 至少要有一个确定类型的形参
2)在可变参数列表 (, …) 的后面不能再跟其他形参
由于可变参数列表中的形参类型不确定, 所以编译器将对传入的实参执行默认 实参提升, 比如传入实参为char型, 将自动提升为int型
3)需要借助 stdarg.h 头文件中的宏迭代获取可变参数列表中的形参

double function(int n_value, ...); // 正确的声明

7.1、基本用法

va_list 类型
va_list类型代表一种数据对象, 该数据对象用于存放可变参数列表中的形参.

va_list ap; // 定义一个用于存放形参的va_list型对象

va_start
函数执行va_list对象的初始化工作, 即将整个形参列表复制到va_list对象中去, 其第一个参数是va_list类型的对象, 第二个参数是可变参数列表之前的那个参数, 用以在指明在整个参数列表中可变参数列表开始的位置. 执行va_start后va_list对象将指向输入的第一个实参…

va_start(ap, n_value); // 初始化ap

va_arg宏函数用以指定函数调用中va_list对象所指向实参的下一个实参的类型(注意默认实参提升), 并返回va_list对象所指实参的下一个实参的值, 同时每调用一次va_arg, va_list对象都自动指向下一个实参. va_arg函数第一个参数为va_list对象, 第二个参数为va_list对象所指实参的下一个实参的类型. 初始化时va_list已经指向第一个实参, 所以第一次调用va_arg时返回第二个实参的值(该实参对应的形参可能已经确定, 也可能在可变参数列表中).

va_end
在获取完参数列表后, 使用va_end无效化va_list对象.

va_end(ap); // 无效化ap

va_copy宏函数用其第二个参数src初始化其第一个参数dest, 两参数都是va_list类型的对象. 如果src已经已经使用va_start初始化或者va_arg移动, 则dest相当于也经过了同样的操作.

#include <stdarg.h>
float average(int n_values, ...); // 声明可变参数的函数
int main(void)
{
    return 0;
}

// 定义可变参数的函数
float average(int n_values, ...)
{
    va_list var_arg;
    int count;
    float sum = 0.0;

    va_start(var_arg, n_values); // va_start宏函数对var_arg对象初始化

    for(count = 0; count < n_values; count += 1)
    {
        sum += va_arg(var_arg, int); // va_arg宏指定下一个实参的类型与值
    }

    va_end(var_arg); // va_end无效化var_arg对象 

    return sum / n_values;
}

8、引用和指针对比

1)引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3)不存在指向空值的引用,但是存在指向空值的指针。
4)指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
5)在底层中,二者都指向同一个地址,效率是一样的

8.1、指针引用场景区分

1)需要不指向任何对象时或者需要在不同时刻指向不同对象时,用指针
2)如果指向一个对象之后不再改变或者重载某个操作符,用引用

9、数组与指针对比

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof§,p 为指针得到的是一个 指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux开发C语言有很多优势和工具可供选择。首先,Linux操作系统本身对C语言的支持非常到位,因为Linux本身就是用C语言编写的。其次,Linux提供了完善的编译环境,包括gcc、as、ld等编译、链接工具,这些工具可以帮助我们将C代码编译成可执行文件。此外,Linux还提供了强大的调试环境,主要是gdb工具,可以帮助我们调试C程序并定位问题。另外,Linux还有丰富的自动编译工具,主要是make工具,可以帮助我们管理项目和自动化编译过程。此外,Linux还有多样化的操作系统选择,如Ubuntu、Red Flag等,可以根据自己的需求选择适合的操作系统。最后,Linux还有浩瀚的开源代码库,可以供我们参考和学习。总之,LinuxC语言开发提供了理想的环境和工具。 #### 引用[.reference_title] - *1* *3* [linux下的C语言开发](https://blog.csdn.net/xpp02/article/details/84374843)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [LinuxC语言开发](https://blog.csdn.net/first_bug/article/details/122540557)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值