本文主要记录c语言编码过程中的一些注意事项。

1 C语言编码注意事项

1.1 申请的动态内存一定要做初始化工作

例如:如下所示的代码,若未对table->bucket指针数组赋初值,该数组中就有可能存在非法值,导致代码访问到非法内存。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int initHashTable(HashTable* table, int bucket_num)
{
   table->bucket_num = bucket_num;
   table->bucket = (HashNode**)malloc(sizeof(HashNode*) * bucket_num);
   if (table->bucket == NULL) {
       printf("Alloc memory failed.\n");
       return -1;
   }
    // 此处一定要做初始化工作,否则可能存在非法内存访问的情况
   memset(table->bucket, 0, sizeof(HashNode*) * bucket_num);
   return 0;
}

1.2 对结构体变量初始化时,需要初始化所有的变量

实例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct ListNode {
    int val;
    struct ListNode *next;
};

ListNode* initNode(int val)
{
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    if (node == NULL) {
        return NULL;
    }
    node->val = val;
     // 注意此处一定要初始化,否则有可能访问到非法内存
    node->next = NULL;
}

1.3 c语言初始化数组的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//Unless that value is 0 (in which case you can omit some part of the initializer and 
//the corresponding elements will be initialized to 0), there's no easy way.

//Don't overlook the obvious solution, though:
int myArray[10] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };

//Elements with missing values will be initialized to 0:
// initialize to 1,2,0,0,0...
int myArray[10] = { 1, 2 }; 

//So this will initialize all elements to 0:
int myArray[10] = { 0 }; // all elements 0

//In C++, an empty initialization list will also initialize every element to 0. This is not allowed with C:
int myArray[10] = {}; // all elements 0 in C++

//Remember that objects with static storage duration will initialize to 0 if no initializer is specified:
static int myArray[10]; // all elements 0

1.4 字符数组初始化的方法

1
2
3
4
5
6
7
8
9
//If you don't want to change the strings, then you could simply do
const char *a[2];
a[0] = "blah";
a[1] = "hmm";

//If you do want to be able to change the actual string content, the you have to do something like
char a[2][14];
strcpy(a[0], "blah");
strcpy(a[1], "hmm");

1.5 16进制字符串转换为整数字符串

1
2
3
4
5
char str[] = "0x1800785";
int num;

sscanf(str, "%x", &num);
printf("0x%x %i\n", num, num); 

1.6 typedef用法

1
2
3
4
5
6
7
// typedef existing_name alias_name;
typedef unsigned int UINT;

typedef struct Node {
    int val;
    struct Node* next;
} Node;

2 C语言中和指针相关知识

2.1 与指针有关的函数声明

1
2
3
4
5
6
7
8
//声明返回指针数组的函数
int (*func(int i))[10]; 
//声明一个指向函数的指针(pf)
bool (*pf)(const string &, const string &); 
//声明一个名字为pf的函数,该函数返回bool*
bool *pf(cosnt string&, const string &);
//声明一个返回函数指针的函数
int (*f1(int))(int *, int);

2.2 与指针有关的数组声明

指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针
数组指针:a pointer to an array,即指向数组的指针
例如:
int* a[4] 指针数组
表示:数组a中的元素都为int型指针
元素表示: *a[i]和*(a[i])是一样的,因为[]优先级高于*

int (*a)[4] 数组指针
表示:指向数组a的指针
元素表示:(*a)[i]  

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int main(){
    int c[4]={1,2,3,4};
    int *a[4]; //指针数组
    int (*b)[4]; //数组指针
    
    b=&c;
    //将数组c中元素赋给数组a
    for(int i=0;i<4;i++){
        a[i]=&c[i];
    }
    
    //输出看下结果
    cout<<*a[1]<<endl; //输出2就对
    cout<<(*b)[2]<<endl; //输出3就对
    return 0;
}

2.3 数组名和指针的区别

2.3.1 数组名不是指针

验证程序:

1
2
3
int data[10];
cout << sizeof(data) << endl;
cout << data << &data << endl;

说明:第一行的输出结果为40,说明data不是一个指针,编译器将data当作一个数组来处理。此处data为包含10个整型元素的数组。

2.3.2 数组名作为常量指针

数组名可以转换为指向其指代实体的指针,而且是一个指针常量。该指针指向数组的第一个元素。

验证程序

1
2
3
4
5
6
char str1[10] = "I Love U";
str1++;			//该语句编译时会出错,因为str1是常量

strcpy(str2, str1);	//标记2
cout << "string array 1: " << str1 << endl;
cout << "string array 2: " << str2 << endl;

说明:标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针,而我们在调用中传给它的却是两个数组名。

2.3.3 指向数组的指针

指向数组的指针则是另外一种变量类型(在32平台下,长度为4),仅仅意味着数组的存放地址。
验证程序

1
2
3
char str1[10] = "I Love U";
char *pStr = str;    //标记1
cout << sizeof(pStr) << endl;

2.3.4 数组名当作函数实参

数组名在当作实参传入函数内部时,失去数组名的特性,成为普通的指针。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>
void arrayTest(char str[]){
    cout << sizeof(str) << endl;
}

int main() {
    char str1[10] = "I Love U";
    arrayTest(str1);
    return 0;
}

说明:程序的输出结果为4。

(1) 数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;
(2) 很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

3 glibc源码阅读

3.1 实用gdb阅读glibc接口的方法

  1. 将glibc源码下载到本地,并解压。
  2. 在gdb中添加glibc源码路径;
  • 方法1:在.gdbinit文件中添加命令directory glibc_src_path
  • 方法2:gdb调试二进制时,增加-d参数:-d directory

3.2 理解glibc的简单方法:使用musl libc库

musl libc库实现了和glibc一样的功能,但是代码更简洁易懂。
可以通过阅读这个代码理解glibc中接口是如何实现的。
musl libc库网址