龙空技术网

在嵌入式设计中使用C ++替代C的准则

嵌入式Linux 445

前言:

现时兄弟们对“c语言程序设计说明书案例”大约比较注重,看官们都想要学习一些“c语言程序设计说明书案例”的相关知识。那么小编在网络上汇集了一些关于“c语言程序设计说明书案例””的相关资讯,希望我们能喜欢,兄弟们一起来了解一下吧!

嵌入式软件应用程序通常是用C编写的。多年以来,C ++被视为自然的后继产品,并且被人们接受,但是C ++的使用增长比预期的要慢得多。

有许多的原因。首先,嵌入式开发工程师非常保守,宁愿使用经过验证的解决方案,也不要使用新颖的解决方案,“ 如果无法解决,请不要解决 ”。

还有经验教训。许多开发人员试图将C ++用于嵌入式应用程序,但失败了。这种故障有时可能归因于开发工具的缺陷,但最常见的归咎于“对台式机之类的嵌入式系统进行处理”语言的不当使用。

在这个由两部分组成的教程中,我们试图解决其中的一些问题,为有效使用C ++嵌入式应用程序提供指导,并说明该语言对嵌入式开发人员有真正的好处。

一般来说,C ++在计算世界中获得普及的主要原因之一就是它的历史。尽管它是一种现代语言,具有面向对象的功能,但它具有向后兼容性,因此很容易采用“学习和应用”。

下面的图1 提供了对该语言的发展的一些见解。在本系列的后面部分,我们将更详细地介绍C / C ++的兼容性。

图1。C ++的族谱

C的局限性

尽管C被广泛地使用,但是它有局限性,因为它不是为嵌入式应用程序设计的,也不是为现在普遍规模的项目设计的。关键限制包括:

1) C非常强大且灵活,因此可能很危险。(它具有低级别的功能,这对于嵌入式系统很有用,但对于那些粗心的人也有很多陷阱。)

2) 程序员需要非常有条理和有纪律

3)程序员需要了解程序在低水平和高水平时的行为(因此难以维护大型项目)

4) 程序员需要应用程序的专业知识

但是,C ++具有强大的面向对象功能,可以极大地帮助解决C的局限性:

1) 将非专业人士的高专长领域封装和隐藏为“对象”;(一个测试用例将在本系列的第2部分的后面展示对专业知识的封装)。

2) 非专家可以直观地使用对象来实现高级概念设计

语言概述

与ANSI C一样,C ++除了具有面向对象的功能之外,还对原始C语言进行了许多增强。C ++不仅仅是C的超集,因为两种语言是并行发展的。

但是,可以逐步学习和应用该语言,如本文稍后所述。目前,以下是一些有用的语言功能的概述:

1)动态内存分配运算符。 运算符new和delete替代了库函数malloc()和free(),并导致了更具可读性和更少错误的代码。

2)功能原型。在C ++中引入并被ANSI C采纳,因此必须强制使用它们。

3)Functionparameter默认值。 函数可能具有用于跟踪参数的默认值,以增强代码的可读性。

4)参考参数。函数参数可以通过引用而不是通过值(副本)传递。这样就可以高效地使用指针,而不会因使用指针而产生错误。这是一个简单的示例函数:

void swap(int&a,int&b)

{

int temp = a;

a = b;

b =温度;

}

4)内联函数。 (小)功能的代码可以声明为“内联”,即,每个呼叫站点都包含实际代码的副本。这可以提高执行速度,但可以增加代码大小。可以使用inline关键字或通过将代码包含在类定义的主体中来实现。这只是给编译器的建议,可能会考虑当前的优化设置。

5)功能重载。 可以使用相同的名称定义多个功能。编译器将通过其唯一数量/类型的参数来区分它们。这样可以使代码更具可读性,而无需进行人为的函数命名。

6)类型安全链接。 在C ++中,所有函数名称都是“混杂”的,它们的名称被修改以反映参数和返回的数据类型。这使链接器可以执行其他跨模块检查,而无需“了解” C ++。这也是实现功能重载的机制。

面向对象的功能

C ++通常被描述为一种面向对象的语言,但这并不是绝对正确的。它实际上是一种具有某些面向对象功能的过程语言(如C)。

语言的主要特征是类的概念。类与C中的结构非常相似,但是具有一些重要的区别和增强:

1)使用关键字class定义一个类。

2) 一个类可能包含代码和数据(不仅是指向函数的指针)

3)可以将 类成员声明为私有成员或公共成员,从而使关键功能被隐藏(封装)

4) 实际上,一个类是具有声明的类名称的新数据类型;数据和对该数据的可能操作已定义

5) 可能会定义(重载)运算符以操作类中的数据,从而导致可读,直观的代码

6)可能包含在创建和销毁对象时自动调用的成员函数(构造函数和析构函数)

7)一类可以从另一类派生而来,继承其所有特征,可以加以美化或替换

模板

C ++的一大特色是模板。该概念可以应用于函数或类定义。这个想法很简单:程序员可以定义类/函数的完整结构和逻辑,而不必理会数据类型(用于参数和内部变量)的规范。

当程序员实例化模板时,这充当编译器生成代码/数据的“配方”(并指示所需的数据类型)。

这是一个功能模板的简单示例:

模板 无效掉期(X&x,X&y) { X温度; temp = x; x = y; y =温度; }

可以这样使用:

int i,j;

浮动a,b;

swap(a,b); //实例#1

wap(i,j); //实例化2

模板为所有程序员提供了“桌面和嵌入式”的强大功能,可以创建高度可重用的代码。嵌入式开发人员总是想知道“成本”,即使用模板会产生多少开销?

从理论上讲,不会有任何开销,因为模板不会自行生成代码。“代码仅在需要时由编译器创建。但是,这是一个问题的原因:编译器一次只能处理一个模块,因此模板实例化基于每个模块。

结果是在应用程序的多个模块中可能生成相同的代码“即,用一组相同的数据类型实例化的模板”。这可能会大大增加内存占用量。这对于台式机编程人员来说可能是无关紧要的,但是对于嵌入式系统开发人员来说可能非常重要。

除了简单地了解正在发生的事情之外,解决该问题的一种可能方法是拥有针对嵌入式工作进行了优化的工具。一种方法是提供一个链接程序,该链接程序可以接受编译器的“提示”并消除冗余(副本)代码。

异常处理

在几乎所有类型的软件中,都有可能发生错误情况,这种情况可能会被软件检测到。通常,这需要对某种代码进行某种“紧急出口”才能优雅地处理错误。

不幸的是,结构化编程语言(如C,C ++等)无法方便地处理这种可能性。创建C ++异常处理系统(EHS)来解决此问题。

EHS在另外三个C ++关键字中体现:try,throw和catch。try块用于控制何时激活异常处理。throw语句用于促进处理错误(异常)。一个catch块“可能有很多,每个对象类型由一个对象类型标识”包含处理错误的代码。

这是一个简单的示例,显示了EHS语法的工作方式:

void scopy(char * str)

{

如果(sizeof(store)+1 <strlen(str))

抛出-1;

strcpy(store,str);

}

void get_string()

{

char buff [100];

cin >>浅黄色

scopy(buff);

}

main()

{

试试

{

get_string();

}

}

抓(int err) { cout <<“字符串太长!”;

}

尽管EHS简化了处理紧急情况所需的编码,但确实要付出代价:生成了额外的代码以使throw语句能够正常工作。嵌入式开发人员总是会警惕额外的开销,因此应监控此额外代码的大小。

嵌入式开发工程师还需要了解有关EHS的两点:

首先,由于编译器无法知道是否将在try块内调用特定代码段,因此将为所有应用程序模块生成额外的代码。更糟糕的是,某些编译器默认情况下启用了EHS,并且程序员有责任将其关闭。因此,如果未使用EHS,则必须检查编译器上的选项以确保不产生开销。

其次, EHS旨在促进针对多种错误情况的不同响应选择。这对于桌面应用程序是完全有意义的。但是,许多嵌入式应用程序都需要非常简单的错误处理。

通常,如果检测到“困难”错误,则会执行系统重置,因为这是恢复稳定运行状态的最佳方法。可以很合理地得出这样的结论:在这种情况下,EHS会被滥用。但是,有两种方法可以以较低的开销使用它,这可能会有利于平衡:

如果在进行抛出时未找到匹配的catch语句,则调用库函数Terminate()。例程可以替换此功能以执行系统重置。

仅使用以下符号即可提供通用捕获处理程序:

赶上(...)

这将捕获任何类型的异常,并导致可测量的更低的代码开销。

语言扩展

使用标准化语言(如C ++)的最大优势之一是代码和程序员的专业技能非常易于移植。在此基础上,将扩展添加到标准语言似乎是愚蠢的,因为这将导致不可移植性。

在理想世界中,这确实是正确的。但是,C ++(如C)是为桌面计算应用程序而设计的,而不是嵌入式的。嵌入式开发人员众多,但仍然只占C ++用户的一小部分。结果,少量的语言扩展可能被认为是有用且可以接受的:

1) 许多为嵌入式设计的编译器都包含一项功能,该功能使数据可以打包,并通过消除数组和结构中的对齐字节来最大程度地利用内存。

为了使此功能真正有效,程序员需要使用该功能将其关闭以用于特定的数据结构,以提高访问速度或提供与某些其他系统或软件的格式兼容性。该替代通常由unpackedextension关键字实现。当全局选项关闭时,可以使用相应的打包关键字来允许打包单个对象。

2)传统上,中断服务例程(ISR)用汇编语言编码有两个原因:执行速度效率和上下文节省。现代编译器生成的代码对于ISR而言肯定足够快,但是常规函数无法促进所需的完整上下文保存和备用返回机制。通常提供一个额外的关键字interrupt来声明一个函数为ISR。

3) 有时不可避免地要使用汇编语言。这通常是因为C ++无法访问CPU功能,“启用/禁用中断是一个常见的例子。

尽管始终可以调用以汇编语言编写的函数,但是可以通过在行中插入必要的汇编代码行来减少开销。通常,可以使用extensionkeyword asm来促进汇编语言伪函数的声明。(#pragma asm也可能。)

4)对于使用C的嵌入式软件开发人员,关键字volatile是必不可少的,因为它具有在线程之间共享或表示I / O设备的变量,这一点很常见。此关键字并非总是在早期的C ++编译器中实现,但显然仍然需要。

C到C ++迁移

的策略 C ++和C之间向后兼容的最大优点是可以逐步完成迁移。一种可能的策略是应用三个阶段的过程:

1) 利用可重用性 ”用C ++编写所有新代码

2) 将C视为C ++”清理现有的C代码以符合C ++要求

3)开始更广泛地使用C ++语言功能

稍后,可以更充分地利用C ++的面向对象功能。

应用可重用性。在链接时,C和C ++之间潜在的不兼容是显而易见的。如前所述,C ++编译器将合并函数名称,以合并有关参数的数量和类型以及返回数据类型的信息。这有助于函数重载,并导致类型安全的链接。AC编译器不会这样做,因此链接C和C ++模块将导致错误。

幸运的是,在C ++中,提供了一种机制来解决此问题:使用外部“ C”构造可以将函数的链接声明为与C兼容,因此:

#ifdef _cplusplus extern“ C”

{

#endif

extern void alpha(int a); extern int beta(char * p,int b); #ifdef _cplusplus }

#endif

清理C ++

的C代码迁移到C ++的下一步是检查和修改现有的Ccode,使其真正与C ++兼容,并且可以由C ++编译器进行毫无问题的处理。结果代码可以称为CleanC。以下是要考虑的关键问题:

1)函数原型 ”这些在C ++中是必需的。

2)类型转换 ”隐式转换在C ++中要严格得多;使用显式强制转换运算符,例如(char)。

3)枚举类型 “ C编译器并没有非常认真地对待它们,因为它们被认为是制作类型化的符号常量的一种手段。在C ++ enum声明中,结果为真实的数据类型。

4)String arraysize ”在C和C ++中,char数组通常用于包含以NULL结尾的字符串。这要求数组具有足够的字符和终止符容量。

在C语言中,不允许使用大于声明的数组大小的字符串来初始化数组,但是可以丢弃NULL终止符。在C ++中不扩展这种宽大处理。一种简单的策略是不指定数组大小,并允许编译器分配空间,因此:

char str [] =“ xyz”;

5)嵌套结构 ”在C中,如果在另一个结构中定义了一个结构,则内部结构也可以由它自己在模块的其他位置声明。在C ++中,内部结构的范围严格限制在外部结构的范围内。因此,以下代码将不是CleanC:

struct out

{

struct in {int i; } m;

int j;

};

内部结构

向外构造

6)多重声明 ”在C语言中,在函数外部声明模块中的变量,然后稍后在模块中再次声明是完全合法的。仅当重新声明不一致时才会引起问题。在C ++中,多个声明是非法的。

7)其他关键字” C ++在已经在C中保留的那些关键字之上引入了许多其他关键字。如果C代码中的标识符冲突,这可能是一个问题。最好的策略是引入命名约定并将其应用于代码。例如,所有函数,类型和变量的首字母均大写,并且所有常量均大写。

C +:更好的C?

在考虑面向对象技术之前,迁移过程的最后一步是逐步应用C ++语言功能。这导致我们可以将混合语言“更好的C”称为“ C +”。可能的功能包括但不限于以下C ++功能:

1)新的 注释符号” C ++使用//运算符引入了新的行尾注释符号。当使用传统的“ / *…* /”表示法(当然仍然可用)时,这会更整洁并解决嵌套问题。

2)Referenceparameters ”默认情况下,在C ++中,与C一样,大多数参数都是通过值(副本)传递的。但是,也有通过引用传递的选项,以避免显式指针使用的复杂性。

3)变量声明放置 ”在C中,必须在块的开头声明自动变量(通常将它们放置在函数的开头)。在C ++中放宽了此限制,使变量可以在首次使用时声明。例如:

对于(int x = 0; x <3; x ++)

4)结构中的构造方法在C ++中,类定义可能包含构造函数和析构函数成员函数,当对象进入和超出范围时,它们会分别自动调用。由于在大多数方面,结构也与类相同,因此可能包括构造函数。这对促进struct变量的自动初始化很有用。

5)内存泄漏 ”通过使用成对的构造函数和析构函数,可以确保不再需要分配的资源(如动态内存),从而避免内存泄漏。

6)异常处理 ”本文前面介绍的EHS并非严格地是一种面向对象的语言功能,因此,在使用C +时应考虑使用它。

嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!

分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!嵌入式物联网学习路径思维导图领取

标签: #c语言程序设计说明书案例