c基础

未归类

  1. 数组不支持变量定义数组大小,如下:未初始化数组错误。只能通过编译时常量#define 定义
    size_t maxlineLen = 128;
    char line[maxlineLen] = {0};
    
  2. 为啥有puts没有gets? 被废除了,不安全。可以使用fgets, getline这类限制大小。一般使用后者不保留\n

  3. 读写函数中有哪些外部分配,有哪些内部分配? 内部分配的:getline 外部分配的:fgets

  4. 一直strtok会发生什么?
    • 最后返回null
    • 原始字符串破坏了,替换为\0
  5. fork子进程分支如果没有exit会发生什么?是必须的嘛? 是的,会发生未定义行为,且会进入父亲代码。

  6. 打印没有结尾\0的 char*

    printf("%.*s", len, ptr); // 打印指定长度。 常用于前面指示len的场景
    
  7. compare mem: mem

  8. byte & 128 != 0

  9. 时间

    CPU时间:程序占用CPU时间。clock_tlong毫秒级。 正常时间:

    • 微秒级:struct timevalgettimeofday()
    • 纳秒级:struct timespecclock_gettime()
    • 毫秒级:time_t , long; struct tm 场景:
  10. 时间戳转换为字符串?
    • ctime(time_t *) :固定格式,带换行。快速使用。
    • 如果要转为指定格式字符串,要先转为struct tm
  11. time_tstruct tm转换:
    • 时间戳转为tm结构:localtime() gmtime()UTC的
  12. tm转字符串:
    • strftime()
  13. 屏幕刷新频率?
    • 使用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";后者常量字符串是编译时候生成在只读段的,这就是正常的指针指向。

  1. 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调用。

results matching ""

    No results matching ""