C 언어 로 구현 되 는 String 라 이브 러 리
C 언어 는 오래된 고급 언어 로 서 문자열 에 대한 지원 이 매우 약 하 다.
입문 할 때 우 리 는 문자열 의 실현 을 위해 ASCII 문 자 를 배열 로 포함 하 는 것 을 알 았 다. 예 를 들 어
char arr[] = "hello world!";
이렇게 길이 가 고정된 배열 의 실현 방식 을 바탕 으로 C 의 문자열 의 길 이 는 가 변 적 이지 않 지만
arr[]
의 내용 은 가 변 적 이다.이러한 디자인 으로 인해 우 리 는 문자열 에 대한 처리 가 매우 번 거 롭 고 위험 할 때 가 많 습 니 다. 제 가 전에 쓴 하 프 만 인 코딩 디 코딩 을 할 때 디 코딩 후의 결 과 를 담 기 위해 서 저 는 아주 큰 정적 배열 이나 동적 할당 메모리 로 함수 가 발생 하 는 길이 가 일정 하지 않 은 문자열 을 만들어 야 합 니 다.
후배 (예 를 들 어 Python / Java, C + + 는 기본적으로 C 의 문법 을 호 환 한다. C + + 는 자신의 string 류 를 실 현 했 지만) 에 비해 C 는 여러 가지 측면 에서 비교적 다른 유형 이다. 예 를 들 어 C 사용
'\0'
은 문자열 의 끝 을 상징 하기 때문에 len(arr)
이러한 작업 의 복잡 도 는 O (n) 에 달 했다. 이것 은 비교적 큰 비용 이 고 Pascal / python 등의 실현 은 모두 O (1) 를 할 수 있다.char
유형 자체 가 가장 짧 은 정형 인 데다 가 C 언어의 약 한 유형의 유형 시스템 이기 때문에 'a'- 32
도 효과 적 인 문법 이 고 Python 에서 이 로 인해 *TypeError*
발생 할 수 있다. 이런 문 제 는 C 언어 가 탄생 한 시대 에 큰 문제 가 아니 었 다. 그 당시 에 문자열 의 처리 수요 가 많 지 않 았 고 C 의 주요 응용 장면 도 비교적 밑바닥 에 있 었 기 때문이다.현재 C 를 선택 한 일부 프로그램 은 빈번 한 처리 문자열 (예 를 들 어
Redis
을 필요 로 하고, 빈번 한 처리 키 값 이 필요 하 다) 을 필요 로 한다. 이런 장면 에 대응 하기 위해 많은 재 미 있 는 자신의 String 실현 이 제기 되 었 다.여기 서 저 는 주로 ccan 의 xstring 과 sds 의 실현 방향 을 소개 합 니 다.
xstring
/**
* struct xstring - string metadata
* @str: pointer to buf
* @len: current length of buf contents
* @cap: maximum capacity of buf
* @truncated: -1 indicates truncation
*/
typedef struct xstring {
char *str;
size_t len;
size_t cap;
int truncated;
} xstring;
xstring *xstrNew(const size_t size)
{
char *str;
xstring *x;
if (size < 1) {
errno = EINVAL;
return NULL;
}
str = malloc(size);//mark 1
if (!str)
return NULL;
x = malloc(sizeof(struct xstring));//mark 2
if (!x) {
free(str);
return NULL;
}
xstrInit(x, str, size, 0);
return x;
}
xstring
구조 체 와 *xstrNew(const size_t size)
이 새로운 xstring
함 수 를 만 드 는 것 을 통 해 ccan
의 이 실현 방향 이 비교적 뚜렷 하 다. xstring
구조 체 자체 가 메모 리 를 차지 하지만 문자열 을 저장 하지 않 는 다. 문자열 은 mark 1 에 저장 되 어 있 고 구조 체 는 mark 2 에 메모리 가 분배 된다.PS:
C 를 사용 하여 데이터 구 조 를 실현 하 는 것 을 막 배 웠 을 때, 나 는 왜 직접적 으로 할 수 없 는 지 의 심 스 러 웠 다.
struct xstring* newStruct(){
struct xstring s;
return &s;
}
나중에 야 스 택 에 있 는 변수 와 동적 으로 분 배 된 변수의 미묘 한 차 이 를 알 게 되 었 습 니 다. s 는 이 함수 가 돌아 온 후에 이미 소각 되 었 습 니 다. 전해 진 이 주 소 는 무효 입 니 다. 그 에 대한 인용 은 세그먼트 오류 (segment fault), 운영 체제, 컴 파일 원리 등 수업 을 통 해 프로그램 설계 언어 에 대해 더욱 깊 은 이 해 를 얻 을 수 있 습 니 다.
그리고 이런 문법 은 당시 에 매우 매력 적 이 었 다. 왜냐하면 malloc 를 사용 하지 않 고 유형 전환 을 강제 할 필요 가 없 었 기 때문이다.
이런 야 지침 은 수정 하기 어 려 운 잘못된 원천 이다. 관심 이 있 는 학생 들 은 Rust 언어의 소유권 시스템 을 배 울 수 있 고 많은 개념 이 매우 재미있다.
|
xstring
| -> | str
| 이 를 통 해 알 수 있 듯 이
xstring
의 실현 중 메모리 가 두 부분 으로 나 뉜 다.참고: xstring 은 컴 파일 러 가 C89 / 90 만 지원 해 야 합 니 다.
sds
redis sds (simple dynamic string) 는 Redis 가 str 의 실현 에 대해 공식 적 으로 sds 실현 에 대한 기술 에 대한 소개 가 있 습 니 다.
여기 서 SDS 가 실현 하 는 주요 세부 사항 을 다음 과 같이 소개 하 겠 습 니 다.
// sds
typedef char *sds;
// sdshdr
struct sdshdr {
// buf
int len;
// buf
int free;
//
// c99(C99 specification 6.7.2.1.16) flexible array member, buf sdshdr ,
// google "flexible array member"
char buf[];
};
위의 실현 과 달리 sds 는 저 장 된 문자열 의 길이 와 남 은 길이 만 저장 하지만 가장 주 목 받 는 것 은 마지막 배열 성명 입 니 다.
char buf[];
구조 체 에서 배열 의 크기 를 밝 히 지 않 았 다 는 것 은 우리 가 배열 에 대한 일 관 된 인상 과 맞지 않 는 것 같 지만 이것 은 합 법 적 인 특성 으로 유연성 배열 이 라 고 한다.
구체 적 인 문법 세부 사항 은 더 이상 소개 하지 않 겠 지만 다음 과 같은 몇 가 지 를 주의해 야 한다.
sizeof(struct sdshdr) == sizeof(len) + sizeof(buf)
, x8664 의 전형 적 인 값 은 8 개의 바이트 (4 + 4) 여야 한다. 이 는 buf[]
가 실제 공간 을 차지 하지 않 고 64 개의 시스템 아래 의 지침 은 8 개의 바이트 가 필요 하 다 는 것 을 의미한다.struct header {
size_t len;
unsigned char data[1];
};
이러한 서법 아래 data
는 unsigned char*
형의 지침 으로 저 장 된 문자열 에 접근 할 수 있 습 니 다.// , , (n-1)
//
// | struct (char) | (n - 1) char |
ptr = malloc(sizeof(struct header) + (n-1));
대비 sds
에서 의 실현, sds
에서 그 어떠한 데이터 도 저장 하지 않 고 메모리 공간 을 차지 하지 않 는 표지 만 있 습 니 다. 모든 데 이 터 는 구조 체 가 차지 하 는 공간 뒤에 저 장 됩 니 다 | struct | str
| /*
* sds buf
*/
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
/*
* sds buf
*/
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
/*
* sds
* init , init sds buf
*
* T = O(N)
*/
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
// init ?
// O(N)
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
// ,
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
// init initlen 0
// init sds buf
// O(N)
if (initlen && init)
memcpy(sh->buf, init, initlen);
//
sh->buf[initlen] = '\0';
// buf sdshdr
return (char*)sh->buf;
}
새로운 sds 를 만 들 때 할당
sizeof(struct sdshdr) + len + 1
크기 의 공간 을 만 듭 니 다. len 은 끝 기 호 를 포함 하지 않 은 용량 을 의미 합 니 다. 마지막 으로 문자열 이 시 작 된 주 소 를 되 돌려 줍 니 다. 이 되 돌아 오 는 주 소 는 일반적인 문자열 로 다른 라 이브 러 리 함수 등에 서 직접 사용 할 수 있 습 니 다. 즉, Redis 가 말 하 는 바 이 너 리 호 환 (내부 에서 도 '0' 끝 을 사용 하기 때 문 입 니 다).동시에 구조 체 의 주 소 는 문자열 의 주소 로 구조 체 의 크기 를 줄 여 얻 을 수 있다.
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
이렇게 하면 sds 는 상수 시간 내 에 문자열 의 길 이 를 얻 을 수 있다.
#include
#include "./src/simple_dynamic_str.h"
int main() {
sds s = sdsnew("Hello World! K&R");
printf("%s
", s);
printf("%zu %zu
", sdslen(s), sdsavail(s));
printf("%c",s[0]);
return 0;
}
:
Hello World! K&R
16 0
H
이런 지침 의 계산 을 통 해 구조 체 의 주 소 를 얻 는 방식 은 비교적 보기 드 문 기술 이다. 나 도 리 눅 스 커 널
task_struct
구조 체 에서 비슷 한 기 교 를 본 적 이 있 을 뿐이다. 물론 그것 은 더욱 복잡 하 다.이런 조작 은 매우 위험 하지만 C 배열 은 이 방면 에서 도 사실 그다지 좋 지 않다 (배열 의 경계 검사 등 이 많 지 않다). 그렇지 않 습 니까?
문자열 이 비교적 짧 을 때 구조 체 가 공간 을 차지 하 는 것 은 비교적 볼 만하 다. 업데이트 버 전의
Redis
은 서로 다른 길이 의 문자열 구조 체 의 정 의 를 최적화 시 켰 다./* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
총결산
이 글 에서 일부 기 교 는 아직도 어렵다. 예 를 들 어
sds
나 도 그 원 리 를 이해 하 는 데 시간 이 좀 걸 렸 다. 이곳 의 두 가지 실현 은 나 개인 이 두 번 째 것 을 더 선 호 하지만 이것 은 2 등 시민 이기 때문에 언어 등급 의 지지 가 없 으 면 경상 이다.그래서 만약 에 문자열 을 대량으로 처리 해 야 한다 면 특히 비 순수 ASCII 코드, 좌회전 자바 / python etc.
reference:
[redis sds(simple dynamic string)]()
[ccan xstring]()
Redis 디자인 과 구현
https://stackoverflow.com/que...
https://redis.io/topics/inter...
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Redis 해시에 대한 완벽한 가이드변경 가능하므로 필요에 따라 쉽게 변경하고 업데이트할 수 있습니다. Redis 해시는 구조가 평평하므로 JSON에서와 같이 여러 수준을 가질 수 없습니다. redis 해시의 명명 규칙은 hash:key 로 입력되므로...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.