这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » The Embedded C++ Programming Guide Lines

共2条 1/1 1 跳转至

The Embedded C++ Programming Guide Lines

菜鸟
2003-09-16 05:14:42     打赏
辞职了, 闲来无事, 译一段东西, 随便看看吧 ...... 原文附后. 嵌入式C++编程指南 ----------------------------------------------------------------------------------- A. 移植:从C语言到C++ A.1 字符常量 注意 在C语言中,字符常量的数据类型是int;在C++中,它的数据类型是char。 例 i = sizeof('a'); 在C语言中,该语句将sizeof(int)的值(通常大于1)赋给变量i;在C++中,该语句将sizeof(char)的值赋给变量i,该值总是等于1。 要点 在进行从C语言到C++的代码移植时,应该重写那些与字符常量的存储长度相关的表达式,以便去除这种依赖性。 A.2 文件范围内的对象声明 注意 在C++的文件范围内,不带存储类型指定符的对象声明表示这就是对象的定义,且该对象具有外部连接。如果该定义不含初始化,则该对象的初始值为0(如同C语言一样,在C++程序里声明的对象必须且只能被定义一次)。 在C语言的文件范围内,不带存储类型指定符,且不含初始化的对象声明表示这只是试探性的定义,这种定义在一个编译单元之内可以出现多次。 例 int a; /* (1) */ int a = 10; /* (2) */ 在C语言中,语句(1)是试探性的定义。由于语句(2)是明确的变量定义,C编译程序将把语句(1)当作纯粹的变量声明。 在C++中,语句(1)和(2)都是变量定义。由于语句(1)的存在,语句(2)就成了重复定义,因此将导致出错。 要点 在C++中,当且仅当文件范围内的对象声明带有显式的"extern"指定符并且不含初始化时,它才仅仅是对象声明(而非定义)。在文件范围内,每个被声明的对象都必须且只能被定义一次。除此之外,所有对该对象的声明都必须带有显式的"extern"指定符并且不含初始化。 A.3 常量类型限定符 注意 在C语言的文件范围内,不带显式存储类型指定符的常量对象都具有外部连接。在C++中,这种对象只具有内部连接。 例 +- file1 --------------------+ | extern const int n; | +----------------------------+ +- file2 --------------------+ | const int n = 10; | +----------------------------+ 在C语言中,file2里的对象n具有外部连接,所以在file1里对n(也具有外部连接)的引用是成立的。在C++中,file2里的对象n只具有内部连接,所以在file1里不能引用n。 要点 若要使C++中的常量对象具有外部连接,必须使用显式的"extern"指定符。 A.4 转换成void指针 注意 C语言能够自动地进行从void *到T *的标准类型转换(T是任意对象类型)。在C++中不存在这样的自动转换,而必须进行强制类型转换。 下列标准C库函数的返回值都是void *: calloc, malloc, realloc, bsearch, memcpy, memmove, memchr, memset 当把上述函数的返回值赋给一个非void类型的指针时,C++要求进行显式的类型转换。 例 int *p; p = malloc(10 * sizeof(int)); 在C++中,对指针p的赋值必须经过显式的类型转换,如: p = (int *)malloc(10 * sizeof(int)); 要点 在C++中,应当使用new运算符来代替calloc, malloc, realloc等函数(参见A.12)。可以忽略memcpy, memmove, memset等函数的返回值(它们只是将其第一个参数转换成void *并返回之)。对于所有其它返回void *的函数(标准库函数或自定义函数),在将其返回值转换成其它类型的指针时,都应当进行显式的类型转换。 A.5 枚举类型 注意 枚举在C语言中相当于整型。程序可以将枚举类型作为整型使用或者相反,无需类型转换。在C程序里可以对枚举对象进行++和--运算。 在C++中,每个枚举都属于不同的类型。C++支持从枚举类型到整型的标准类型转换,反之则不行。在C++程序里不能对枚举对象进行内建的++和--运算,也不允许对其进行任何的复合赋值(如+=)。 例 enum RGB {red, green, blue} rgb; ++rgb; 如果这里的++是内建运算符的话,++rgb对C++来说就是错误的语句。它等价于: rgb = rgb + 1; 除非使用如下的强制类型转换,否则上面的语句同样是错误的: rgb = RGB(rgb + 1); 最佳的选择是在RGB类型里重载运算符++,如: RGB &operator++(RGB &x) { return x = RGB(x + 1); } 要点 在进行从C程序向C++程序的移植时,应当为运算符++和--提供类型安全的实现方式,以满足枚举类型的需求。 A.6 类型转换、参数声明或sizeof表达式里的类型定义 注意 在C语言的类型转换表达式、参数声明或sizeof表达式里可以进行类型定义。在C++中这是不允许的。 例 void func (struct TAG {int a;} st) { ... } 这里,类型TAG在参数声明里定义。 要点 参数声明里用到的类型应该在包含该函数声明的范围或更大的范围内定义。类型转换表达式或sizeof表达式里用到的类型应该在包含该表达式的范围或更大的范围内定义。 A.7 超越局部对象定义的控制转移 注意 在C语言中,goto或switch语句可以在代码块的范围内进行超越对象定义的控制转移,同时也可能绕过对该对象的初始化。在C++中这是不允许的。 例 goto LABEL; { int v = 0; ... LABEL: ... } 假设标号LABEL:之后的代码不要求变量v必须被初始化为0,则上面的程序在C语言中就是合法的。但在C++中,这必将产生错误。 要点 不要使goto或switch语句绕过对局部对象的定义和初始化。 A.8 字符数组的初始化 注意 在C语言中,可以使用比数组的容量还多一个字符的字符串(含串尾字符'\0')来初始化字符数组。在C++中这是不允许的。 例 char s[3] = "abc"; 数组的容量是3,而字符串的长度是4。这在C语言中是合法的,在C++中则不允许。 要点 不要使用比数组的容量更长的字符串(含串尾字符'\0')来初始化字符数组,而必须以该字符串的确切长度作为数组容量(如char s[4] = "abc";)。 然而,如果希望即使在字符串的长度发生改变时也总是能够获得预期的结果,建议使用不指明数组容量的初始化方式(如char s[] = "abc";)。 A.9 原型声明 注意 C++程序要求在调用函数之前声明其原型。在C语言中,调用未经声明的函数是允许的。此外,C++程序认为函数声明"f()"等价于"f(void)",即无参函数。而在C语言中,该声明表示参数的个数和类型未定。 例 extern void func(); .... sub(); func(0); 在C++中,对函数sub的调用是错误的,因为没有声明其原型。对函数func的调用同样出错,因为该函数的声明指出它是不带参数的。 要点 在调用函数之前一定要声明其原型。为了强调对函数f的调用是不带参数的,应该把它的声明写成"f(void)"。 A.10 C++增加的关键字 注意 下列C++关键字不是C语言的关键字: asm bool catch class const_cast delete dynamic_cast explicit false friend inline mutable namespace new operator private protected public reinterpret_cast static_cast template this throw true try typeid typename using virtual wchar_t 例 int class, new, old; 该声明在C语言中是合法的,在C++则不允许。 要点 不要把C++关键字作为标识符使用。 A.11 嵌套类型的作用域 注意 在C语言中,定义在结构或联合之内的类型名与该结构名或联合名有着完全相同的作用域。在C++中,嵌套类型名的作用域则局限在相应的结构或联合之内。 例 struct S { int a; struct T { int t; } b; int c; enum E {V1, V2} e; }; struct T x; enum E y; 对x和y的声明在C语言中是合法的,在C++中则不允许。在C++中,类型名T和E不能作用于结构S的定义之外。 要点 不要定义嵌套的类型名,除非所有对该名称的引用都位于相应的结构或联合之内。 A.12 动态内存管理 注意 C++不保证运算符new和delete对相同的内存采取与函数malloc和free相同的内存管理策略。因此,只有事先用"new"获取的内存才能用"delete"来释放;只有事先用malloc(或calloc, realloc)获取的内存才能用free来释放。 例 int (*p)[10]; p = (int (*)[10])malloc(sizeof(*p)); ... delete p; 该delete操作的行为是未定义的。 要点 不要在C++程序中使用malloc, calloc, realloc, free等函数;应该只用new和delete来管理内存。 A.13 '/'后面的'/*' 注意 紧跟在符号'/'之后的C语言风格的注释"/* */"将被解释为C++风格的注释"//..."。 例 i = j //* comment */ k; 字符序列"//"将被解释为注释分隔符。该语句将被解释为"i = j",而非"i = j / k;"。 要点 不要把C语言风格的注释"/* */"紧跟在符号'/'之后书写。 B. 代码长度 B.1 对象的初始化 注意 初始化一个对象有多种方式。其中某些方式会生成不必要的临时对象,从而导致代码长度的增加。 例 T x(i) // (1) T x = i; // (2) T x = T(i) // (3) T x; // (4) x = i; (1) 该语句直接调用对象x的构造函数,不会生成临时对象,就如同调用: x.T(i); // apply constructor to x (2) 在某些实现中,该语句象(1)一样直接调用对象x的构造函数。在另一些实现中,它先构造一个临时对象,再用该对象来初始化x,就如同调用: temp.T(i); // apply constructor to temp x.T(temp); // apply copy constructor to x temp.~T(); // apply destructor to temp (3) 该语句等价于(2)。 (4) 该语句调用类型T的缺省构造函数来初始化x,然后使用赋值运算符为x赋新值。该赋值操作能够释放x当前占用的资源并为其分配新的资源。 x.T(); // apply default constructor to x x.operator = i; // apply assignment operator to x 要点 优先使用上述方式(1)进行对象声明。 B.2 inline指定符 注意 内联展开能够减少函数调用和返回时的开销,但可能导致代码长度的增加。缺省情况下,在类内部定义的成员函数都将被内联展开。 要点 只对小函数使用inline指定符。不适合进行内联展开的成员函数应该在类之外加以定义,从而使其免于被内联展开。 B.3 返回值临时对象 注意 如果调用某个函数时,该函数返回某个对象的值,则为此将创建并销毁一个临时对象,从而导致代码长度和执行时间的增加。 例 class Matrix { int a, b; public: Matrix &operator += (const Matrix &); friend: Matrix operator +(const Matrix &, const Matrix &); }; Matrix operator +(const Matrix &, const Matrix &) { ... } void func() { Matrix a, b; a = a + b; // (1) a += b; // (2) } 语句(1)调用了运算符+,该操作返回一个Matrix型对象的值。在某些实现中,这将创建(并随后销毁)一个Matrix型的临时对象。 语句(2)调用了运算符+=,该操作不会生成Matrix型的临时对象。 要点 对于类的对象,应当使用复合赋值运算符(比如用+=而不是+和=),以避免创建和销毁不必要的临时对象。 B.4 运算符new和delete 要点 为不同的类分别实现其所需的运算符new和delete,可以提高动态内存管理的速度,改善内存的使用。 B.5 全局对象的初始化 注意 对各全局对象进行初始化的次序与具体实现有关。然而,在单个编译单元之内的初始化次序必定与对象声明的次序相同。 例 /* file1 */ int a = f(); /* file2 */ int b = f(); /* file3 */ int f(void) { static int a = 0; return a++; } 程序可能将变量a初始化为0,将变量b初始化为1,或者相反,取决于具体实现所选择的初始化次序。为了得到确定的初始化次序,可以把变量b的声明从file2移到file1中,如: /* file1 */ int a = f(); int b = f(); /* file3 */ int f(void) { static int a = 0; return a++; } 初始化次序将是先a后b。 要点 在C++中,应当避免使程序依赖于不同编译单元之内的各全局对象的初始化次序。 C. 运行速度 C.1 类对象数组的new和delete操作 注意 声明一个类对象数组时,程序将对每个数组元素调用其构造函数。同样,在该数组超出生存期之后,程序将对每个数组元素调用其析构函数。构造和析构函数所需的处理时间可能会长得超出预期,这在实时处理过程中将导致问题。 要点 如果处理时间是重要的考量因素,则在程序中应当避免创建和销毁较大的类对象数组。 C.2 循环程序中的对象声明 注意 如果在循环程序中声明一个类对象,则其构造和析构函数在每次循环中都将被调用。构造和析构的开销将降低循环程序的运行速度。 例 for (i = 0; i < 1000; i ++) { FOO a; ... } 要点 不要在循环程序中声明类对象。应当把此类声明放在循环之外。 D. 基于ROM的代码 D.1 ROM中的常量对象 注意 一般而言,如果满足下列条件,常量对象就可以存放在ROM中: -- 具有静态的存储期; -- 由常量表达式进行初始化; -- 是POD(plain old data)类型的对象。 POD类型是指: -- 标量(数值、枚举或指针)类型; -- 满足下列条件的类、结构或联合类型:所有数据成员都是公共的;所有数据成员都是POD类型的;没有自定义的构造或析构函数;没有基类;没有虚函数; -- 由POD类型的元素组成的数组。 例 static const char lang[] = "EC++"; class A { int a; public: A(); ~A(); }; const A x; 数组lang可以存放在ROM中,对象x则不行。 要点 常量对象如果需要存放在ROM中,则应当将其声明成POD类型,并用常量表达式进行初始化。 ---------------------------------------------------------------------------------



关键词: Embedded     Programming     Guid    

菜鸟
2003-09-16 05:15:00     打赏
2楼
The Embedded C++ Programming Guide Lines -------------------------------------------------------------------------------- A. Migrating from C language to C++ language A.1 Character constant Note In C, a character constant has type int. In C++, it has type char. Example i = sizeof('a'); In C, this stores sizeof(int) (which is likely to be greater than 1) into i. In C++, it stores sizeof(char) (which is always 1) into i. Guideline When migrating code from C to C++, rewrite expressions that depend on the size of a character constant to remove the dependency. A.2 Object declaration at file scope Note In C++, a declaration of an object at file scope without a storage class specifier is a definition of that object with external linkage. If the definition does not have an initializer, the object has initial value 0. (As in C, an object declared in a C++ program must be defined exactly once.) In C, a declaration of an object at file scope without a storage class specifier and without an initializer is a tentative definition, which may appear more than once in a translation unit. Example int a; /* (1) */ int a = 10; /* (2) */ In C, (1) is a tentative definition. Since (2) is unequivocally a definition, C treats (1) as a mere declaration. In C++, both (1) and (2) are definitions. In the presence of (1), (2) is a duplicate definition, and therefore an error. Guideline In C++, a declaration of an object at file scope is just a declaration (and not also a definition) if and only if it has an explicit extern specifier and no initializer. Each object declared at file scope must be defined exactly once. All but one declaration must have both an explicit extern specifier and no initializer. A.3 const type qualifier Note In C, a const-qualified object at file scope without an explicit storage class specifier has external linkage. In C++, it has internal linkage. Example +- file1 --------------------+ | extern const int n; | +----------------------------+ +- file2 --------------------+ | const int n = 10; | +----------------------------+ In C, object n of file2 has external linkage, so it can satisfy the reference to n (also with external linkage) in file1. In C++, object n in file2 has internal linkage, and will not satisfy the reference to n in file1. Guideline Const-qualified objects with external linkage must have an explicit extern specifier. A.4 Conversion to void * Note In C, there is a standard conversion from void * to T * (for any object type T). In C++, there is no such conversion. Such conversions require a cast in C++. The following Standard C library functions return void *: calloc, malloc, realloc, bsearch, memcpy, memmove, memchr, memset C++ requires an explicit cast when assigning the return value of such a function to a pointer to non-void type. Example int* p; p = malloc(10 * sizeof(int)); In C++, the assignment to p requires an explicit cast, as in p = (int *)malloc(10 * sizeof(int)); Guideline In C++, use operator new instead of calloc, malloc, or realloc. (See item A.12). Ignore the return value of a memcpy, memmove, and memset. (They just return their first argument converted to void *.) For all other functions that return void * (standard or user-defined), use an explicit cast when converting the return value to another pointer type. A.5 Enumeration type Note In C, enumerations are integral types. A program can convert from an enumeration type to an integral type, and back, without a cast. A C program can apply ++ and -- to an enumeration object. In C++, each enumeration is a distinct type. There are standard conversions from enumeration type to integral types, but not from integral types to enumeration types. A C++ program cannot apply built-in ++ and --, nor any compound assignment (such as +=) to an enumeration object. Example enum RGB { red, green, blue } rgb; ++rgb; ++rgb is an error in C++ if it uses the built-in ++ operator. It means the same as rgb = rgb + 1; This is also an error unless you write it with a cast: rgb = RGB(rgb + 1); The best alternative is to implement an operator++ for type RGB, as in RBG &operator++(RGB &x) { return x = RGB(x + 1); } Guideline When converting a C program to C++, provide typesafe implementations for operator++ and operator-- as needed for enumeration types. A.6 Type definition in cast, parameter declaration, or sizeof Note In C, types can be defined in a cast expression, parameter declaration or sizeof expression. In C++ they cannot. Example void func(struct TAG { int a; } st) { ... } Here, TAG is defined in the parameter declaration. Guideline Define a type used in a parameter declaration in the scope enclosing the function declaration, or some larger enclosing scope. Define a type used in a cast expression or sizeof expression in the scope enclosing the expression, or some larger enclosing scope. A.7 Transfer of control past the definition of a local object Note In C, a goto or switch statement may transfer control beyond the definition of an object at block scope, possibly bypassing initialization. In C++, it may not. Example goto LABEL; { int v = 0; ... LABEL: ... } This is valid in C, assuming the code following LABEL: does not depend on v being initialized to 0. It is always an error in C++. Guideline Do not use goto or switch statements to bypass initialization of a local object. A.8 Character array initialization Note A C program can initialize an array of characters using a string literal that defines one more character (counting the terminating '\0') than the array can hold. A C++ program cannot. Example char s[3] = "abc"; The size of the array is three, though the size of the string literal is 4. This is valid in C, but not C++. Guideline Do not initialize an array of characters using a string literal with more characters (including the '\0') than the array. Therefore, it is necessary to specify the correct size of a string literal (char s[4] = "abc";). However, because the result of the expectation always can be obtained even if the size of the string literal is changed, the method of not describing the size (char s[] = "abc";) is recommended. A.9 Prototype declaration Note A C++ program requires that you declare a function prototype before calling the function. In C, a call to an undeclared function is permissible. Moreover, a C++ program interprets the function declarator 'f()' as equivalent to 'f(void)' -- a function with no arguments. In C, the same declaration leaves the number and types of the parameters unspecified. Example extern void func(); .... sub(); func(0); The call to function 'sub' is an error because there is no prototype declaration. The call to function 'func' is also an error because its declaration says it has no arguments. Guideline Always declare the prototype before calling a function. To emphasize that function 'f' is called with no arguments, write its declarator as 'f(void)'. A.10 Keywords added in C++ Note The following C++ keywords are not keywords in C: asm bool catch class const_cast delete dynamic_cast explicit false friend inline mutable namespace new operator private protected public reinterpret_cast static_cast template this throw true try typeid typename using virtual wchar_t Example int class, new, old; This declaration is valid in C, but not in C++. Guideline Do not use a C++ keyword as an identifier. A.11 Scope of nested types Note In C, the name of a type defined inside a struct or union is actually in same scope as the name of the enclosing struct or union. In C++, the name of the nested type is within the scope of the enclosing struct or union. Example struct S { int a; struct T { int t; } b; int c; enum E { V1, V2 } e; }; struct T x; enum E y; The declarations for x and y are valid in C, but not in C++. In C++, the names T and E are not in scope outside the definition of struct S. Guideline Do not define the name of a type as nested unless all uses of that name are also in the scope of the enclosing struct/union. A.12 Dynamic memory management Note There is no guarantee that new and delete apply the same memory management policy to the same memory as do malloc and free. Therefore, a program cannot delete memory unless that memory was previously acquired by new, and it cannot free memory unless that memory was acquired by malloc (or calloc or realloc). Example int (*p)[10]; p = (int (*)[10])malloc(sizeof(*p)); .... delete p; The delete expression has undefined behavior. Guideline In C++, avoid malloc, calloc, realloc and free; use only new and delete. A.13 '/*' after '/' Note Writing the C-style comment '/* */' immediately after the token '/' is interpreted as the C++-style comment '//' instead. Example i = j //* comment */ k ; The sequence '//' is interpreted as a comment delimiter. The expression is interpreted not as 'i = j / k;' but as 'i = j'. Guideline Avoiding writing a C-style comment '/**/' immediately after the token '/'. B. Guidelines for Code Size B.1 Object initialization Note There are various ways to specify the initialization of an object. Some initializations generate unnecessary temporary objects, and result in larger code size. For example: T x(i) // (1) T x = i; // (2) T x = T(i) // (3) T x; // (4) x = i; // (1) This applies a constructor directly to object x without using a temporary object, as if by calling: x.T(i); // apply constructor to x (2) In some implementations, this applies a constructor directly to x as above. In others, it constructs a temporary object, and initializes x from the temporary, as if by calling: temp.T(i); // apply constructor to temp x.T(temp); // apply copy constructor to x temp.~T(); // apply destructor to temp (3) This is the same as (2). (4) This initializes x using T's default constructor, then later assigns a new value to x using an assignment operator. The assignment operator may release resources that x is using and acquire new resources. x.T(); // apply default constructor to x x.operator=(i); // apply assignment operator to x Guideline Use declarations of form (1) above in preference to the other forms. B.2 Inline specifier Note Inline expansion reduces the overhead of function entry and exit, but it may increase code size. Member functions in class definitions are expanded inline by default. Guideline Use inline specifier for only small functions. A member function for which inline expansion is not appropriate should be defined outside the class definition, so that it is not inline expanded. B.3 Temporary objects for return values Note Calling a Function that returns an object by value may create and destroy a temporary object, thus increasing by code size and execution time. Example class Matrix { int a, b; public: Matrix &operator+=(const Matrix &); friend Matrix operator+(const Matrix &, const Matrix &); }; Matrix operator +(const Matrix &, const Matrix &) { ... } void func() { Matrix a,b; a = a + b; // (1) a += b; // (2) } (1) calls operator+, which returns a Matrix by value. In some implementations this creates (and later destroys) a temporary Matrix object. (2) calls operator+=, which generates no temporary Matrix objects. Guideline For objects of class types, use compound assignment operators (such as += in preference to + and =) to avoid creating and destroying temporary objects unnecessarily. B.4 Operators new and delete Guideline Implement class-specific operators new and delete as needed to improve the speed and memory utilization of dynamic memory management. B.5 Initialization of global objects Note The order of initialization of global objects is implementation-dependent. However, the order of initialization in a single translation unit is guaranteed to be the order of declaration. Example file1 file2 file3 int a = f(); int b = f(); int f(void) { static int a = 0; return a++; } The program may initialize 'a' with 0 and 'b' with 1, or vice versa, depending on the order the implementation chooses to initialize them. It is possible to make the initialization order well defined by moving the declaration of 'b' in file2 to file1, as in: file1 file2 file3 int a = f(); int f(void) int b = f(); { static int a = 0; return a++; } The order of initialization is a, then b. Guideline Avoid coding which depends on the initialization order of global objects across translation units. C. Guidelines for speed C.1 new and delete for array of class objects Note Declaration of an array of class objects calls its element's constructor for each element. The program calls the destructor for each element when the array goes out of scope. The processing time of the constructor/destructor may be unexpectedly long. This can be a problem for real-time processing. Guideline Avoid creating and destroying a large array of class objects during time-critical processing. C.2 Object declaration in loops Note When a class variable is declared in a loop, its constructor and destructor are called in each iteration. The overhead of construction and destruction may slow the loop. Example for (i = 0; i < 1000; i++) { FOO a; ... } Guideline Avoid declaring a class variable inside a loop. Declare it outside the loop instead. D. Guidelines for ROMable code D.1 const objects in ROM Note In general, a const-qualified object can reside in ROM if it: -- has static storage duration, -- is initialized by a constant expression, and -- has a POD (plain old data) type. A POD type is any of: -- a scalar (arithmetic, enumeration or pointer) type -- a class, struct, or union all of whose data members are public and of POD types, and with no user-defined constructor or destructor, no base classes, and no virtual functions -- an array with elements of POD type Example static const char lang[] = "EC++"; class A { int a; public: A(); ~A(); }; const A x; 'lang' may be placed in ROM; 'x' may not. Guideline Declare objects to be placed in ROM with POD types and with constant initializers. NOTE: The form of presentation used here, and several of the specific guidelines, were inspired by the excellent book by Thomas Plum and Dan Saks, 'C++ Programming Guidelines' (Plum Hall Inc., 1991).

共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]