CMU C++ Tips 笔记

偶然发现 CMU 的 wiki,里面有 C/C++ 的编写小技巧和提醒,特意挑了些自以为的重点做了笔记,其中可能还混杂了我在其他地方看到的内容,详细内容 wiki 可以看文末链接。

1、preprocessor

1.1、universal character names

The universal character name \Unnnnnnnn designates the character whose 8-digit short identifier (as specified by ISO/IEC 10646) is nnnnnnnn.

Similarly, the universal character name \unnnn designates the character whose 4-digit short identifier is nnnn (and whose 8-digit short identifier is 0000nnnn).

1.2、#include_next

例如有个搜索路径链,在#include中,它们的搜索顺序依次是A,B,C,D和E。在B目录中有个头文件叫a.h,在D目录中也有个头文件叫a.h,如果在我们的源代码中这样写#include <a.h>,那么我们就会包含的是B目录中的a.h头文件,如果我们这样写#include_next <a.h>那么我们就会包含的是D目录中的a.h头文件。#include_next <a.h>的意思按我们上面的引号包含中的解释来说就是“在B目录中的a.h头文件后面的目录路径(即C,D和E)中搜索a.h头文件并包含进来)。#include_next <a.h>的操作会是这样的,它将在A,B,C,D和E目录中依次搜索a.h头文件,那么首先它会在B目录中搜索到a.h头文件,那它就会以B目录作为分割点,搜索B目录后面的目录(C,D和E),然后在这后面的目录中搜索a.h头文件,并把在这之后搜索到的a.h头文件包含进来。这样说的话大家应该清楚了吧。

还有一点是#include_next是不区分<>和""的包含形式的。

现在来说说为什么要引人这条指令!

假如,你要创建一个新的头文件,而这个新的头文件和现在已有的头文件有相同的名字,而且你想用你的这个新的头文件,那么你要做的就是把这个新的头文件放在#include指令的搜索路径的前面,即是在旧的头文件的前面新的头文件首先被搜索到,这样你就可以使用你这个新的头文件。但是你在另一个源代码文件中想使用旧的头文件了,那怎么办!有个办法就是使用绝对路径来搜索,那么就不存在这样的问题了。问题出在,如果我们把头文件的位置移动了,移到了其它的目录里了,那我们就得在相应的源码文件中修改这个包含的绝对路径,如果一个源码文件还好,但如果是大型工程的话,修改的地方多了就容易出问题。

1.3、macro side effects

#define ABS(x) (((x) < 0) ? -(x) : (x))
int m = ABS(++n);
// equal to below
m = (((++n) < 0) ? -(++n) : (++n));

// how to avoid
++n;
int m = ABS(n);

// or prefer inline or static functions to function-like macros
// Inline substitution is not textual substitution, nor does it create a new function.
static inline int iabs(int x) {
  return (((x) < 0) ? -(x) : (x));
}

// GCC's __typeof extension makes it possible to 
// declare and assign the value of the macro operand to a temporary of the same type 
// and perform the computation on the temporary, 
// consequently guaranteeing that the operand will be evaluated exactly once. 
#define ABS(x) __extension__ ({ __typeof (x) tmp = x; \
                    tmp < 0 ? -tmp : tmp; })

Expressions used as arguments to the standard assert() macro should not have side effects.

1.4、STDC_VERSION

Checks __STDC_VERSION__ to ensure that a pre-C11 compiler will fail to compile the code, rather than invoking undefined behavior.

#if __STDC_VERSION__ < 201112L
#error This code requires a compiler supporting the C11 standard or newer
#endif

2、tips

2.1、memset

int buff_2[10];
memset(buff_2,1,sizeof(buff_2));  
for(int i = 0;i<10;i++)
{
    printf("%d ",buff_2[i]);
}

是的,变量的类型变了,打印的结果是:

16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009

是不是觉得很奇怪,接下来,我们把这句:printf("%d ",buff_2[i]);

改为:printf("0X%x ",buff_2[i]);

得到的结果是:

0X1010101 0X1010101 0X1010101 0X1010101 0X1010101 0X1010101 0X1010101 0X1010101 0X1010101 0X1010101

是不是感觉很蹊跷?

首先,在我的设备上,int是4字节,所以buff_2总共40字节,memset会对40个1字节赋值0X01,而不是对10个4字节赋值0X01。

所以不要用memset对非字符型数组赋初值!

2.2、快速判断 char * 是否为 NULL

!!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")

2.3、Structure Packing

When passing a pointer to a structure across a trust boundary to a different trusted domain, the programmer must ensure that the padding bytes and bit-field storage unit padding bits of such a structure do not contain sensitive information.

使用 memcpy 时要注意结构体可能的字节对齐。

// GCC allows specifying declaration attributes using the keyword __attribute__((__packed__)). 
// When this attribute is present, the compiler will not add padding bytes for memory alignment 
// unless an explicit alignment specifier for a structure member requires the introduction of padding bytes.
struct test {
  int a;
  char b;
  int c;
} __attribute__((__packed__));

// Microsoft Visual Studio  supports #pragma pack() to suppress padding bytes
#pragma pack(push, 1) /* 1 byte */
struct test {
  int a;
  char b;
  int c;
};
#pragma pack(pop)

// _Alignof(类型名称)  查询操作数类型的对齐要求。
printf("alignof(float[10]) = %zu\n", alignof(float[10])); // output: 4

Because these padding values are unspecified, attempting a byte-by-byte comparison between structures can lead to incorrect results.

void compare(const struct s *left, const struct s *right) { 
  if ((left && right) &&
      (0 == memcmp(left, right, sizeof(struct s)))) {
    /* ... */
  }
}
// 上面的比较方式是错误的,除非是非 padded 的 struct 类型,需要使用下面这种方式
void compare(const struct s *left, const struct s *right) { 
  if ((left && right) &&
      (left->c == right->c) &&
      (left->i == right->i) &&
      (0 == memcmp(left->buffer, right->buffer, 13))) {
    /* ... */
  }
}

2.4、volatile

If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.

2.5、restrict

restrict 的含义是由编程者向编译器声明,在这个指针的生命周期中,只有这个指针本身或者直接由它产生的指针(如 ptr + 1)能够用来访问该指针指向的对象。

他的作用是限制指针别名,帮助编译器做优化.如果该指针与另外一个指针指向同一对象,那么结果是未定义的。

int add (int* __restrict__ a, int* __restrict__ b)
{
    *a = 10;
    *b = 12;
    return *a + *b;
}

编译器已经得知 a, b 两指针不会指向同一单元(此点应由编程者保证),所以在这里会减少一次内存的访问。

a 中一定是 10, b 中一定是 12, 所以编译器可以在这里做优化直接返回 22.

注意,编译器是无法自行在编译期检测两个指针是否 alias。如使用 restrict,程序员也要遵守契约才能得出正确的代码(指针不能指向相同数据)。

Developers should be aware that C++ does not support the restrict qualifier, but some C++ compiler implementations support an equivalent qualifier as an extension.

#include <stddef.h>
void f(size_t n, int *restrict p, const int *restrict q) {
  while (n-- > 0) {
    *p++ = *q++;
  }
}
  
void g(void) {
  extern int d[100];
  /* ... */
  f(50, d + 1, d); /* Undefined behavior */
  f(50, d + 50, d); /* Defined behavior : d is effectively divided into two disjoint objects. */
}

The assignment of restrict-qualified pointers to other restrict-qualified pointers within the same block has undefined behavior.

2.6、__builtin_sadd_overflow

This compliant solution uses the GNU extension __builtin_sadd_overflow, available with GCC, Clang, and ICC:

void f(signed int si_a, signed int si_b) {
  signed int sum;
  if (__builtin_sadd_overflow(si_a, si_b, &sum)) {
    /* Handle error */
  }
  /* ... */
}

other operator:

__builtin_ssub_overflow__builtin_smul_overflow

2.7、uintptr_t

Any valid pointer to void can be converted to intptr_t or uintptr_t and back with no change in value. (See INT36-EX2.) The C Standard guarantees that a pointer to void may be converted to or from a pointer to any object type and back again and that the result must compare equal to the original pointer. Consequently, converting directly from a char * pointer to a uintptr_t, as in this compliant solution, is allowed on implementations that support the uintptr_t type.

#include <stdint.h>
#include <assert.h>  

void f(void) {
  char *ptr;
  /* ... */
  uintptr_t number = (uintptr_t)ptr; 
  /* ... */

// Any valid pointer to void can be converted to intptr_t or uintptr_t or their underlying types 
// and back again with no change in value. 
// Use of underlying types instead of intptr_t or uintptr_t is discouraged, however, because it limits portability

  intptr_t i = (intptr_t)(void *)&i;
  uintptr_t j = (uintptr_t)(void *)&j;
  
  void *ip = (void *)i;
  void *jp = (void *)j;
  
  assert(ip == &i);
  assert(jp == &j);

}

2.8、指定输出字符串的位数

#include <stdio.h>
  
void func(const char *name) {
  char filename[128];
  sprintf(filename, "%.123s.txt", name); // 只拿它的 123 位
  snprintf(filename, sizeof(filename), "%s.txt", name); // 安全写法,防溢出
}

2.9、Cast characters to unsigned char before converting to larger integer sizes

However, this rule is applicable only in cases where the character data may contain values that can be interpreted as negative numbers.

For example, if the char type is represented by a two’s complement 8-bit value, any character value greater than +127 is interpreted as a negative value.

static int yy_string_get(void) {
  register char *c_str;
  register int c;
 
  c_str = bash_input.location.string;
  c = EOF;
 
  /* If the string doesn't exist or is empty, EOF found */
  if (c_str && *c_str) {
    /* Cast to unsigned type */
    c = (unsigned char)*c_str++;
 
    bash_input.location.string = c_str;
  }
  return (c);
}

2.10、Unnamed namespaces

ODR-use [ISO/IEC 14882-2014]

A function or object is ODR-used if the address of the entity is taken, the function is called, or a reference is bound to the object. When a function or object is ODR-used, its definition must exist within the program or else the program is ill-formed.

Unnamed namespaces are used to define a namespace that is unique to the translation unit, where the names contained within have internal linkage by default.

Production-quality C++ code frequently uses header files as a means to share code between translation units. A header file is any file that is inserted into a translation unit through an #include directive. Do not define an unnamed namespace in a header file. When an unnamed namespace is defined in a header file, it can lead to surprising results. Due to default internal linkage, each translation unit will define its own unique instance of members of the unnamed namespace that are ODR-used within that translation unit. This can cause unexpected results, bloat the resulting executable, or inadvertently trigger undefined behavior due to one-definition rule (ODR) violations.

跟在头文件用 static 一样,也会导致 UB 的行为。

3、modern c++ use

3.1、c++ 11 及其之后的单例模式

template<typename T>
class Singleton{
public:
    static T& getInstance() {
        static T value_; // 静态局部变量
        return value_;
    }
    Singleton(const Singleton&) = delete; // 拷贝构造函数
    Singleton& operator=(const Singleton&) = delete; // =运算符重载
private:
    Singleton(); // 私有化构造方法
    ~Singleton();
};

static local variable:

The initialization of such a variable is defined to occur the first time control passes through its declaration; for multiple threads calling the function, this means there’s the potential for a race condition to define first.

3.2、多值返回与获取

std::tuple<llvm::Reloc::Model, unsigned, bool>
fun{
    return std::make_tuple(llvm::Reloc::PIC_, 2U, false);
}

std::tie(RelocationModel, PICLevel, IsPIE) = ParsePICArgs(TC, Args);

参考&推荐阅读

C/C++ 中的 restrict 关键字:https://jzwdsb.github.io/2018/03/restrict_in_cpp/

CMU 的 wiki:https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard
gcc:预处理语句--#include和#include_next:[https://blog.csdn.net/fjb2080/article/details/5247494]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值