C 언어 로 구현 되 는 String 라 이브 러 리

8668 단어 redistrickstringc
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 개의 바이트 가 필요 하 다 는 것 을 의미한다.
  • 위의 서법 은 C99 only 입 니 다. 이 특성 은 다음 과 같은 서법 에서 나 와 야 합 니 다.
    struct header {
      size_t len;
      unsigned char data[1];
    };
    이러한 서법 아래 dataunsigned 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...

    좋은 웹페이지 즐겨찾기