c基础
未归类
- 数组不支持变量定义数组大小,如下:未初始化数组错误。只能通过编译时常量
#define
定义size_t maxlineLen = 128; char line[maxlineLen] = {0};
-
为啥有
puts
没有gets
? 被废除了,不安全。可以使用fgets
,getline
这类限制大小。一般使用后者不保留\n
-
读写函数中有哪些外部分配,有哪些内部分配? 内部分配的:
getline
外部分配的:fgets
- 一直
strtok
会发生什么?- 最后返回
null
- 原始字符串破坏了,替换为
\0
- 最后返回
-
fork
子进程分支如果没有exit
会发生什么?是必须的嘛? 是的,会发生未定义行为,且会进入父亲代码。 -
打印没有结尾\0的
char*
printf("%.*s", len, ptr); // 打印指定长度。 常用于前面指示len的场景
-
compare mem:
mem
-
byte & 128 != 0
-
时间
CPU时间:程序占用CPU时间。
clock_t
,long
毫秒级。 正常时间:- 微秒级:
struct timeval
;gettimeofday()
- 纳秒级:
struct timespec
;clock_gettime()
- 毫秒级:
time_t
,long
;struct tm
场景:
- 微秒级:
- 时间戳转换为字符串?
ctime(time_t *)
:固定格式,带换行。快速使用。- 如果要转为指定格式字符串,要先转为
struct tm
。
time_t
和struct tm
转换:- 时间戳转为tm结构:
localtime()
gmtime()UTC的
- 时间戳转为tm结构:
tm
转字符串:strftime()
- 屏幕刷新频率?
- 使用usleep实现,0.1s, 0.2秒感知用户输入
重载冗杂
sizeof并不是一个函数,而是一个操作符。
- 操作数是变量:不必加括号。 sizeof *p 获取p指针指向对象的字节数
- 操作数是类型:必须加括号。sizeof(int) *p。
这里含义是什么? 强制转换int型之后 sizeof?还是int长度乘以p? 实践:是后者。
int main(){ char a = '5'; int* p = &a; int q = sizeof(int) * p; printf("%d", q); return 0; }
结构体
柔性数组
必须作为结构体最后一个字段。 问题,redis ziplist 压缩列表希望柔性数组后面再来一个zlend末尾标记,这就会冲突!
typedef struct {
unsigned int zlbytes; // 压缩列表总字节数
unsigned int zltail; // 到最后一个节点的偏移量
unsigned short zllen; // 压缩列表中节点数
unsigned char entries[]; // 节点数据(变长)
unsigned int zlend;
} ziplist;
解决:额外检测zlend字段,创建内存时候,加上zlend字节。但是结构不包含
向前声明
typedef struct ConnectionListener ConnectionListener;
typedef struct {
int fd;
ConnectionListener *listener;
// 其他字段
} Connection;
struct ConnectionListener {
int port;
char *bindAddr;
Connection *conn;
} ;
引起的问题,“封装”:
pointer or reference to incomplete type "struct Pager" is not allowed C/C++(833)
// pager.h
typedef struct Pager pager;
// pager.c
struct Pager {
int size;
};
// main.c
#include "pager.h"
Pager* p;
p->size;
在pager.c
之外,不能直接获取字段,无法感知。只能通过函数接口访问。
// pager.c
int pager_get_size(Pager* p)
{
return p->size;
}
如果pager
作为一个模块,还有page1.c
等模块内部文件,需要直接访问属性。常常我们使用pagerInt.h
作为内部头文件,提供结构体完整定义。而pager.h
作为对外头文件,提供向前声明,封装。
强制结构体/变量对齐。
场景:现在pool结构后面,要跟数据区域。但是整个内存要求8字节对齐,pool结构lock属性尺寸不能保证是8字节对齐的。
typedef struct {
// meta
uint32_t magic;
uint32_t version;
uint32_t cap;
uint32_t used;
//
FreeBlock freeListHead; // 空闲链表头
//
pthread_mutex_t lock; // 内存池锁
// data
} Pool;
解决:
__attribute__((aligned(8)))
是编译器的扩展语法,能够强制结构体/变量对齐。
这个叫编译器属性,不是C语言语法或者宏,一般我们通过宏展开添加#define ALIGN8 __attribute__((aligned(8)))
``attribute((aligned(8))) struct pool {};
int x attribute((aligned(16)))`
位字段
官方标准是int , unsigned int ,signed int
,没有unsigned char
一次声明
struct A {}; // 定义类型 A
struct A a1; // 创建变量 a1
struct {} a2; // 匿名 struct,变量是 a2,不能再创建相同类型变量。 常用在结构体字段内。
预处理
前向声明,避免包含头文件。
// proto.h
typedef struct Conn Conn;
// conn.h
typedef struct Conn Conn;
字符串
分配策略:string
类函数是原地修改,内部分配吗?
除了strdup
内部会分配,其余不会主动malloc。
strtok, strcat
都会原地修改dest
复制:一些结构体内有char*
属性,什么时候该直接赋值,什么时候该dump:
基本上都要dump~
char* buf; c->buf = "aaaa";
后者常量字符串是编译时候生成在只读段的,这就是正常的指针指向。
strtok
will keep a global var, so multi variable error!
char* s1_tok = strtok(s1, "/");
char* s2_tok = strtok(s2, "/");
s1_tok = strtok(NULL, "/"); // line 3
s2_tok = strtok(NULL, "/")
line3 strtok will influence line4.
right way:
char* save1,*save2;
char* s1_tok = strtok_r(s1, "/", &save1);
char* s2_tok = strtok_r(s2, "/", &save2);
s1_tok = strtok(NULL, "/", &save1); // line 3
s2_tok = strtok(NULL, "/", &save2);
IO
snprintf
返回打印的字符数。
printf
行缓冲行为。默认遇到\n
或者缓冲区满才会刷新的标准输出。程序推出前会自动fflush(stdout)
。
下面代码, printf不会打印。因为行缓冲。
int main(int argc, char const *argv[])
{
PoolContext ctx = {NULL,-1};
pool_create(&ctx);
printf("??");
signal(SIGINT, sigintHandler); // CTRL+C
void* ptr= pool_alloc(16, &ctx);
uint64_t* cp = (uint64_t*)ptr;
*cp = 0XFFFFFFFFFFFFFFFF; // 测试写入
// pool_free(ptr, &ctx);
while (!stop) {
sleep(1);
}
puts("stopped");
pool_close(&ctx);
pool_cover(&ctx);
pool_destroy(&ctx);
return 0;
}
GLIB
GLIB文档 glib是跨平台的C工具库。相关地,glibc(GNU C)是linux的标准C库,linux运行基础。
C标准分为语法和实现。
- C标准库:就是一组标准头文件,规定实现含义。
- C运行库:各平台自己实现,并添加一些扩展。比如LINUX的glibc和MS的MSVCRT glib有6个主要模块:
GObject
:面向对象特性:继承、信号、属性等GIO
:IO抽象(文件、流、网络)GLib
:基础工具。高级数据结构、内存管理、字符串处理、时间等GModule
: 跨平台动态加载模块接口?GThread
: 跨平台线程API和线程池支持。GMainLoop
:事件循环机制。GTree
平衡二叉搜索树
随机
整数:
int x = rand() % 100; // 0~99
浮点:
double r = rand() / (double)RAND_MAX; // [0, 1)
double r2 = 90.0 + r * 20.0; // [90.0, 110.0)
时间种子:
srand((unsigned(time(NULL))
随机字符:
char c = 'A' + rand() % 26;
布尔值:
bool b = rand() & 1;
ncurses
关于界面刷新和用户按键。 用户按键按下是有时长的, 如果1秒界面刷新,即意味着只有间隔1s才检测。 如果0.1秒,那就是每秒检测10次。显然会流畅很多。
安全信号处理
volatile sig_atomic_t stop = 0;
sig_atomic_t 类型表示 对于数据的访问不会被信号中断。
volatile 用于告知编译器,该变量的值可能会在编译器无法察觉的情况下被修改(例如,由信号处理程序修改)。防止编译器不必要优化,每次都从内存读取,不可使用缓存。
signal(SIGINT, sigintHandler); // CTRL+C
while (!stop) {
}
// 资源清理
指针加减
void*
不允许加减,比如内存池通过offset + base指针定位需要转为char*
LIBEVENT
退出程序
| 方式 | 场景 | 特点 |
| —————— | ——————– | ————————————————— |
| return-main
| | 正常退出,会执行atexit()
注册的清理函数。 |
| exit(int status)
| 库或者子函数主动退出 | 立即终止程序,会执行atexit()
注册的清理函数。 |
| _exit(status)
| fork
子进程出错调用 | 立即终止,不清理,不刷新缓冲。 |
| abort()
| 致命错误,如断言失败 | 异常终止,不清理,不刷新缓冲 |
| raise(SIGTERM);
| | 信号方式的自杀式终止,触发信号处理程序 |
指针
函数指针,函数类型使用:
方法1:
定义一个函数类型:typedef void callback(struct EventLoop* evp, void* data);
指向一个函数的指针:callback* call;
等价于void (*call)(struct EventLoop* evp, void* data)
方法2:更推荐
定义一个函数指针类型:typedef void (*callback)(struct EventLoop* evp, void* data);
声明一个函数指针类型变量:callback call
等价于void (*call)(struct EventLoop* evp, void* data)
赋值:
函数名直接赋值,赋值对象是指针:callptr = myfunc;
等价于显示取地callptr = &myfunc
但不能的是 calltype = myfunc;❌
结构体中形式:
typedef struct
{
int (*parse)(void *ctx, sds *buf); // Parse from net read. Out to upper.
int (*encode)(void *ctx, sds *buf); // Encode from upper. Out to net.
} proto_handler_t;
优先级
&p->r
取结构体成员地址。等价于&(p->r) == &((*p).r)
*p->r
访问结构体成员内容。等价于*(p->r)
整数和void*
一般对于端口小型整数。
int callback(void* arg)
{
int port = (intptr_t)arg;
}
int port =9988;
callback((void*)(intptr_t)port);
设计
注册-回调解耦机制。
每一层对下层直接调用,对上层进行回调。
A暴露函数声明,B实现&注册,A调用。