# Các API cơ bản trong C và cách implement chúng ## 1. Strlen(const *char str) Đây là 1 API nhận vào tham số là 1 chuỗi và trả về độ dài của chuỗi đó. Có thể thấy, API này vô cùng đơn giản và có thể được implement như sau: ``` #include<stdio.h> #include<stdlib.h> int _strlen(char* str){ int i = 0; while(str[i++] != '\0'); return i-1; } int main(){ char s[] = "hello world!"; printf("%d\n",_strlen(s)); return 0; } ``` ### Output:``12`` Để có thể implement api này, chúng ta chỉ cần cho vòng lặp chạy từ đầu cho đến khi nào gặp phải kí tự ```'\0'``` thì cho dừng vòng lặp, từ đó trả về kết quả cuối cùng là độ dài chuỗi. ## 2. Memcpy(void* source, void* destination, size_t N) Hàm memcpy là 1 api nhận vào 3 tham số: nguồn muốn copy, copy đến đâu và bao copy bao nhiêu byte. Chúng ta có thể implement api này bằng cách dùng vòng lặp để copy `N` phần tử của source vào destination như sau: ``` #include<stdio.h> #include<stdlib.h> #include<string.h> void _memcpy(void *destination, void *source, int N){ char *CDes = (char*)destination; char *Csoure = (char*)source; for(int i = 0; i < N; i++){ CDes[i] = Csoure[i]; } } int main(){ char csrc[100] = "helloworld"; _memcpy(csrc+5, csrc, 5); printf("%s\n", csrc); return 0; } ``` ### Output:`hellohello` ## 3. Memmove(void * destination, const void * source, size_t N) Về cơ bản, chức năng của memmove cũng tương tự như memmcpy đó là copy N byte từ destination vào source, tuy nhiên điểm khác biệt lớn nhất chính là memmove cho phép thao tác trên các vùng data trùng lặp trong khi đó nếu dùng memcpy có thể gây thay đổi dữ liệu. Để hiểu được sự khác biệt đó, ta cần xét đoạn code sau: ``` #include<stdio.h> #include<stdlib.h> #include<string.h> void _memcpy(void *destination, void *source, int N){ char *CDes = (char*)destination; char *Csoure = (char*)source; for(int i = 0; i < N; i++){ CDes[i] = Csoure[i]; } } int main(){ char csrc[100] = "helloworld"; _memcpy(csrc+5, csrc, strlen(csrc)+1); printf("%s\n", csrc); return 0; } ``` ### Output: `hellohellohelloh` Ta có thể thấy output có vẻ không đúng ý của chúng ta khi nó trả ra `hellohellohelloh` trong khi thứ ta muốn lại là `hellohelloworld`. Điều này xảy ra là do vùng destination và source là 2 vùng nhớ nằm đè lên nhau, dẫn đến việc khi copy data từ source qua destination làm thay đổi chính nội dung của source: ``` helloworld |--------| : source |---| : destination ``` Chính vì nhược điểm đó của memcpy nên hàm memmove đã ra đời với giải pháp là sử dụng 1 vùng nhớ tạm để lưu giữ data cho source từ đó tránh sự sai lệch như trên: ``` #include<stdio.h> #include<stdlib.h> #include<string.h> void _memmove(void *destination, void *source, int N){ char *CDes = (char*)destination; char *Csource = (char*)source; char *tmp = (char*)malloc(N); for(int i = 0; i < N; i++){ tmp[i] = Csource[i]; } for(int i = 0; i < N; i++){ tmp[i] = Csource[i]; } for(int i = 0; i < N; i++){ CDes[i] = tmp[i]; } free(tmp); } int main(){ char csrc[100] = "helloworld"; _memmove(csrc+5, csrc, strlen(csrc)+1); printf("%s\n", csrc); return 0; } ``` ### Output: `hellohelloworld` ## 4. Strcpy() và Strncpy() Cả 2 hàm đều dùng để copy văn bản từ source vào destination, duy chỉ có `strncpy()` nhận thêm tham số N để có thể copy N kí tự từ source. Chúng ta có thể impplement cả 2 hàm như sau: ``` #include<stdio.h> #include<stdlib.h> #include<string.h> void _strcpy(char *destination, char *source){ for(int i = 0; i <= strlen(source); i++){ destination[i] = source[i]; } } void _strncpy(char *destination, char *source, int N){ for(int i = 0; i < N; i++){ destination[i] = source[i]; } } int main(){ char source[100] = "hello world!"; char des[100] = ""; _strcpy(des,source); printf("%s\n", des); _strncpy(des,source+6,5); printf("%s\n", des); return 0; } ``` ### Output: ``` hello world! world world! ```` ## 5. Write(int fileDescriptor, void *buffer, size_t bytesToRead) Hàm write là 1 hàm khá đặc biệt vì nó là 1 system call nên ta không thể implement 1 cách thông thường mà phải dùng đến code inline asm, sau đây là cách implement hàm read cho gcc x86_64: ``` #include<unistd.h> #include<string.h> #include<stdlib.h> #include<stdint.h> void _write(int64_t fd, char *buffer, int64_t N){ asm volatile( "mov $1, %%rax\n" "mov %[fd], %%rdi\n" "mov %[msg], %%rsi\n" "mov %[N], %%rdx\n" "syscall" : :[fd] "r" (fd), [msg] "r" (buffer), [N] "r" (N) : "rax", "rdi", "rsi", "rdx" ); } int main(){ _write(1,"hello\n",6); return 0; } ``` ### Output: ``` hello ``` ## 6. Read(int fd, void* buf, int N) Tương tự, chúng ta cũng có thể implement hàm read bằng inline asm: ``` #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<stdint.h> void _read(int64_t fd, void *buf, int64_t N){ asm volatile( "mov $0, %%rax\n" "mov %[fd], %%rdi\n" "mov %[buf], %%rsi\n" "mov %[N], %%rdx\n" "syscall" : :[fd] "r" (fd), [buf] "r" (buf), [N] "r" (N) :"rax", "rdi", "rsi", "rdx" ); } int main(){ char a[1001]; _read(0,a,100); printf("%s ",a); return 0; } ``` ### Input ``` hello ``` ### Output ``` hello ``` ## 7. Printf(const char* buffer, ...) ``` #include<stdio.h> #include<unistd.h> #include<stdarg.h> #include<stdlib.h> #include<stdbool.h> int IntLength(int num){ int res = 0; while(1){ res++; num = num / 10; if(num == 0) break; } return res; } int FloatLength(float num){ int res = IntLength((int)num); num -= (int)num; num *= 10; while(num){ res++; num -= (int)num; num *= 10; } return res; } void intToStr(int num, char *buffer){ int num_length = IntLength(num); for(int i = num_length-1; i >= 0; i--){ buffer[i] = (num%10)+'0'; num = num/10; } buffer[num_length] = '\0'; } void floatToStr(float num, char *buffer){ intToStr((int)num,buffer); int num_i_length = IntLength((int)num); num -= (int)num; num *= 10; buffer[num_i_length] = '.'; int curPos = num_i_length+1; while(num){ buffer[curPos] = (int)num+'0'; num -= (int)num; num *= 10; curPos++; } buffer[curPos] = '\0'; } void _printf(char *buffer, ...){ va_list valist; va_start(valist,buffer); int i = 0; bool isFormatChar = false; char *numHolder; while(buffer[i] != '\0'){ if(!isFormatChar){ if(buffer[i] == '%'){ isFormatChar = 1; } else{ putchar(buffer[i]); } } else{ if(buffer[i] == 'd'){ int num = va_arg(valist,int); int num_length = IntLength(num); numHolder = (char*)malloc(sizeof(char)*num_length); intToStr(num,numHolder); write(1,numHolder,num_length); free(numHolder); } else if(buffer[i] == 'f'){ float num = va_arg(valist,double); numHolder = (char*)malloc(256); floatToStr(num,numHolder); int i = 0; while(numHolder[i] != '\0'){ putchar(numHolder[i]); i++; } free(numHolder); } else if(buffer[i] == 'c'){ putchar(va_arg(valist,int)); } else if(buffer[i] == '%'){ putchar('%'); } isFormatChar = 0; } i++; } va_end(valist); } int main(){ _printf("an int %d, a float %f and a char %c",12,3.14,'h'); printf("\n"); return 0; } ``` ## Output ``` an int 12, a float 3.14 and a char h ```