C Basic Notes
编程习惯
Macro(宏)
括号
尽量添加足够的括号,减少宏定义的二义性
特殊用法
#
: 字符串化##
: 强制连接符do { ... } while (0)
: 防止语法错误
头文件
缺少标准库头文件
缺少函数原型
链接成功 - 链接器自动装载库函数,不影响程序执行 只警告,不报错
覆盖标准库函数原型
- 定义过多参数原型,调用时传入过多参数,函数正确执行(无视多余参数)
- 定义缺少参数原型,调用时传入不完整参数,函数错误执行,误把 0xc(%ebp),0x10(%ebp),…等更多内存单元当作函数参数
缺少宏定义
链接失败 - 宏定义会被识别为函数,但链接器查找不到相应库函数
防止重复包括头文件
#ifndef _FILENAME_H_
#define _FILENAME_H_
...
...
...
#endif
头文件不申请内存单元
除了全局共享静态变量外, 头文件中的定义不允许申请实际的内存单元
检查
边界检查
- 空/满栈检查
- 参数合法性检查 e.g elemSize > 0 检查
指针检查
- Alloctor 失败,需添加 NULL 检查:
- assert
- exit
类型转换
机器码转换
- 有符号类型转换: 进行符号扩展
- 无符号类型转换: 进行零扩展
Pointer Tips and Best Practice
Error Prone Pointers
int i = 37;
float f = *(float *)&i;
float f = 7.0;
short s = *(short *)&f;
- 悬挂指针
- 未初始化
- 改写未知区域
- 下标越界
- 内存上溢 e.g
gets(string);
- 指针相关运算符优先级与结合性
- 返回局部变量的地址
- 重复释放内存空间
- 内存泄漏 e.g 未释放空间/未释放部分深度空间(多维数组)
- 不能引用 void 指针指向的内存单元
Debugging Malloc
处理 void 指针
Tips: 中途运用强制类型转换,使得 void 指针可以执行指针加减运算
void *target = (char *)void_pointer + ...;
利用 void 指 针实现 Generic
通用型 Swap 函数
void swap(void *vp1, void *vp2, int size) {
char buffer[size];
memcpy(buffer, vp1, size);
memcpy(vp1, vp2, size);
memcpy(vp2, buffer, size);
}
通用型 Search Function
实现
void *lsearch(void *key, void *base, int n, int elemSize,
int (*cmp_fn)(void *, void *)) {
for (int i = 0;i < n;i++) {
void * elemAddr = (char *)base + i * elemSize;
if (cmp_fn(key, elemAddr) == 0) {
return elemAddr;
}
}
return NULL;
}
int 实例
int IntCmp(void *elem1, void *elem2) {
int *ip1 = (int *)elem1;
int *ip2 = (int *)elem2;
return *ip1 - *ip2;
}
int array[] = {4, 2, 3, 7, 11, 6},
size = 6,
target = 7;
// 应进行强制类型转换
int * found = (int *)lsearch(&target, array, 6, sizeof(int), IntCmp);
if (found == NULL) {
printf("Not Found");
} else {
printf("Found");
}
string 实例
int StrCmp(void *vp1, void *vp2) {
// 必须进行强制类型转换
char *s1 = *(char **)vp1;
char *s2 = *(char **)vp2;
return strcmp(s1, s2);
}
char *notes[] = {"Ab", "F#", "B", "Gb", "D"},
*target = "Eb";
char ** found = lsearch(&target, notes, 5, sizeof(char *), StrCmp);
泛型数据结构
通用型栈
typedef struct {
void *elements;
int elemSize;
int logLen;
int allocLen;
} stack;
void StackNew(stack *s, int elemSize);
void StackDispose(stack *s);
void StackPush(stack *s, void *elemAddr);
void StackPop(stack *s, void *elemAddr);
void StackNew(stack *s, int elemSize) {
// 参数合法性检查
if (s->elemSize <= 0) {
perror("ElemSize <= 0");
return;
}
s->elemSize = elemSize;
s->logLen = 0;
s->allocLen = 4;
s->elements = (int *)malloc(s->allocLen * elemSize);
// NULL检查
if (s->elements == NULL) {
perror("No Mem");
exit(0);
}
}
void StackPush(stack *s, void *elemAddr) {
// 满栈检查
if (s->logLen == s->allocLen) {
s->allocLen *= 2;
s->elements = (int *)malloc(s->elements, s->allocLen * s->elemSize);
}
void *target = (char *)s->elements + s->logLen * s->elemSize;
memcpy(target, elemAddr, s->elemSize);
s->logLen++;
}
void StackPop(stack *s, void *elemAddr) {
// 空栈检查
if (s->logLen == 0) {
perror("Empty Stack");
return;
}
s->logLen--;
void *source = (char *)s->elements + s->logLen * s->elemSize;
memcpy(elemAddr, source, s->elemSize);
}
Tools
Valgrind - GitHub Repo
Useful Functions
memset
free
free 函数会回退 4/8 字节,取出 heap 块的长度/信息,根据此信息进行 heap 块的释放.
Strings
strdup
string duplicate - char *strdup(string)
封装 allocator 细节
strchr and strstr
返回字符/串在字符串中出现的位置(地址)
strtok
strcasecmp
getopt
解析命令行参数, 轻松地提取以 - 或 / 开头的参数
I/O
String Scanf
可以用作简易匹配读取函数
// 提取除 http:// 外的字符串
sscanf(buf, "http://%s", url_part);
Exceptions
perror(string) - 用来将上一个函数发生错误的原因输出到标准设备(stderr)
Process
Fork and Execve
- fork(): 创建当前进程的拷贝
- execve(): 用另一程序的代码代替当前进程的代码
int execve(char *filename, char *argv[], char *env_p[])
void fork_exec(char *path, char *argv[]) {
pid_t pid = fork();
if (0 != pid) {
printf("Parent: created a child %d\n", pid);
} else {
printf("Child: exec-ing new program now\n");
execv(path, argv);
}
printf("This line printed by parent only!\n");
}
Other
getpid()
.wait(int *child_status)
/waitpid(pid)
.exit()
.
Threads
PThread
typedef unsigned long int pthread_t;
/**
* create thread
* @param {指向线程标识符的指针} pthread_t *__thread
* @param {设置线程属性} __const pthread_attr_t *__attr
* @param {线程运行函数的起始地址} void *(*__start_routine) (void *)
* @param {运行函数的参数} void *__arg
*/
extern int pthread_create __P ((
pthread_t *__thread,
__const pthread_attr_t *__attr,
void *(*__start_routine) (void *), void *__arg)
);
/**
* 等待线程
* @param {被等待的线程标识符} pthread_t __th
* @param {一个用户定义的指针,它可以用来存储被等待线程的返回值} void **__thread_return
*/
extern int pthread_join __P ((pthread_t __th, void **__thread_return));
/**
* 退出线程
* @param {函数的返回代码} (void *__retval)) __attribute__ ((__noreturn__)
*/
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
// 一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码 ESRCH
// 以下为互斥锁相关函数
pthread_mutex_init
pthread_mutexattr_init
/**
* 设置属性 pshared
* PTHREAD_PROCESS_PRIVATE
* PTHREAD_PROCESS_SHARED
*/
pthread_mutexattr_setpshared
/**
* 设置互斥锁类型
* PTHREAD_MUTEX_NORMAL
* PTHREAD_MUTEX_ERRORCHECK
* PTHREAD_MUTEX_RECURSIVE
* PTHREAD_MUTEX_DEFAULT
*/
pthread_mutexattr_settype
pthread_mutex_lock
pthread_mutex_unlock
pthread_delay_np
InitThreadPackage;
ThreadNew;
ThreadSleep;
RunAllThreads;
SemaphoreNew(int > 0);
SemaphoreWait(lock);
SemaphoreSignal(lock);
Semaphore
- 哲学家就餐问题
- 将 Semaphore 变量的值在允许范围内(不至于使得线程锁失效)使得其取最大值,减少线程阻塞
- EmptyBuf 8, FullBuf 0
- 双向通信,互相唤醒 -
Writer:sw(empty),ss(full);
Reader:sw(full),ss(empty);
void SellTickets(int agent, int *ticketsNum, Semaphore lock) {
while (true) {
// 当 lock == 0 时,当前进程阻塞, 等待 lock > 0
// 当 lock > 0 时, 当前进程继续进行, 并且 lock--
SemaphoreWait(lock);
if (*ticketsNum == 0) break; // 票已售磬
(*ticketsNum)--; // 售出一张票
printf("Sell One Ticket.\n");
// lock++ 使得 lock > 0
// 若有其他进程调用了SemaphoreWait, 且因之前 lock == 0 而被阻塞, 则此时其他进程可继续进行
SemaphoreSignal(lock);
}
// break to here
// 作用同循环内的 Signal 函数
SemaphoreSignal(lock);
}