wcslen 源码 实现 学习

2022-05-05  本文已影响0人  onedam

vscode 连接 wsl 的c调试 good
2022年5月2日 学了下 strlen 和 wcslen
musl的实现可读性好。 glibc 优化到了汇编

#include <wchar.h>

size_t wcslen(const wchar_t *s)
{
    const wchar_t *a;
    for (a=s; *s; s++);
    return s-a;
}
//删掉了一些宏,方便理解。
size_t strlen(const char *s)
{
    const char *a = s; 
    for (; *s; s++);
    return s-a;
}

ulibc的

//strlen.c 
#include "_string.h"
#ifdef WANT_WIDE
# define Wstrlen wcslen
#else
# define Wstrlen strlen
#endif

size_t Wstrlen(const Wchar *s)
{
    register const Wchar *p;

    for (p=s ; *p ; p++);

    return p - s;
}
libc_hidden_weak(Wstrlen)
//ulibc更加简洁。 wcslen和strlen 根据宏来一个方法
//wcslen.c
#define WANT_WIDE
#include "strlen.c"
// http://c.biancheng.net/c/assert/ 
// 在操作指针时,一定要保证在指针有效内存空间内操作,不然 Segmentation fault  段错误。 被操作系统kill.
// c编译器内置宏 https://zhuanlan.zhihu.com/p/409044316
// ANSI:即 char,可用字符串处理函数:strcat( ),strcpy( ), strlen( )等以str打头的函数。
// UNICODE:wchar_t是Unicode字符的数据类型,它实际定义在里:
int main(void)
{
    // assert(NULL);
    printf("文件: %s,函数: %s %s %s \n",__FILE__ , __func__,__ASSERT_FUNCTION,__DATE__); //几个c编译器内置变量
    char destination[25];
    char *blank = " ", *c = "C++", *Borland = "Borland";

    strcpy(destination, Borland);
    strcat(destination, blank);
    strcat(destination, c);

    printf("is: %s\n", strchr(destination, 'a'));
    printf("is2: %s\n", my_strchr(destination, 'a+lskjdsafsfsfa'));
    printf("%s\n", destination);

// 字符串分割 
    char str[80] = "This is - www.runoob.com - website";
    // const char s[2] = "-";
    char* s = "-";
    char *token;
    /* 获取第一个子字符串 */
    token = strtok(str, s);
    /* 继续获取其他的子字符串 */
    while (token != NULL)
    {
        printf("%s\n", token);
        token = strtok(NULL, s);
    }
//调用 strcat 必须保证目标地址有足够的已分配的内存用于存储结果。通常需要用 malloc 提前分配内存 或 定义一个足够大的静态 char[] 。
    // char* one1="hao",one2=" ca";
    char one1[100]="hao";
    // char *one1=malloc(100);
    char* one2=" ca";
    char* one3="ab2行者";  //wcslen本程序是7 . 这里会有各种奇怪长度。因为wcslen会4个字节计算一个字符直到遇到0
    char* one4=L"ab2行者";
    char* one5=L"ab2行者6789十了ab"; //超过10行则调用 xmmm寄存器 wsclen.s 优化的汇编...
    // hexdump -C str.c  查看assicc码 22 是双引号"
    printf("strcat: %s \n",strcat(one1,one2));
    // printf("%d \n",strlen("wanglaowu"));
    // printf("%d \n",strlen("行者"));  // strlen 计算的是字节 不是字符。
    // printf("%d \n",strlen(L"行者")); 
    // printf("%d \n",wcslen(L"行者")); //wsl x64下 调用的是__wcslen_sse2 用汇编实现的 
    printf("one3 strlen: %d \n",strlen(one3));
    printf("one3 wcslen: %d \n",wcslen(one3));
    printf("one4 strlen: %d \n",strlen(one4));
    printf("one4 wcslen: %d \n",wcslen(one4));
    printf("one5 wcslen: %d \n",wcslen(one5));
    printf("%d \n",wcslen(L"a冯哥2"));
    return 0;
}
// vscode 调试控制台 下 gdb 查看内存 
// -exec x /10xw 0x5555555560b8
image.png

首先 strlen 和 wcslen 都是遇到内存为 0 的时候停止计算
strlen 计算 L 开头的utf-16(网上一般叫宽字符,实际上是utf-16编码的) 还是按字节来数的。 strlen 计算L开头的字符串就是 1 或 2 。因为4个字节为一组, 例如 a 就是 62 00 00 00 行 就是 4c 88 00 str 遇到0就停止。
不是L开头的字符串,是文件的编码,现在一般都是utf-8.


d72f2e27f53c9fb0f05361feeecdd8e.png
image.png

printf("z9: %d \n",strlen("xa冯哥2")); // 9 good
上面程序 4!!后面加上这行输出。则变为 了 6. 遇0则止 ....

wcslen.s 汇编代码 10个字符 40个字节内的 前面几行优化
/home/feng/glibc_source/sysdeps/i386/i686/multiarch/wcslen-sse2.S

#if IS_IN (libc)
# include <sysdep.h>
# define STR    4

    .text
ENTRY (__wcslen_sse2)
    mov STR(%esp), %edx

    cmp $0, (%edx)
    jz  L(exit_tail0)
    cmp $0, 4(%edx)
    jz  L(exit_tail1)
    cmp $0, 8(%edx)
    jz  L(exit_tail2)
    cmp $0, 12(%edx)
    jz  L(exit_tail3)
    cmp $0, 16(%edx)
    jz  L(exit_tail4)
    cmp $0, 20(%edx)
    jz  L(exit_tail5)
    cmp $0, 24(%edx)
    jz  L(exit_tail6)
    cmp $0, 28(%edx)
    jz  L(exit_tail7)

    pxor    %xmm0, %xmm0

    lea 32(%edx), %eax
    lea 16(%edx), %ecx
    and $-16, %eax

    pcmpeqd (%eax), %xmm0
    pmovmskb %xmm0, %edx
    pxor    %xmm1, %xmm1
    test    %edx, %edx
    lea 16(%eax), %eax
    jnz L(exit)

    pcmpeqd (%eax), %xmm1
    pmovmskb %xmm1, %edx
    pxor    %xmm2, %xmm2
    test    %edx, %edx
    lea 16(%eax), %eax
    jnz L(exit)

    pcmpeqd (%eax), %xmm2
    pmovmskb %xmm2, %edx
    pxor    %xmm3, %xmm3
    test    %edx, %edx
    lea 16(%eax), %eax
    jnz L(exit)

    pcmpeqd (%eax), %xmm3
    pmovmskb %xmm3, %edx
    test    %edx, %edx
    lea 16(%eax), %eax
    jnz L(exit)

    and $-0x40, %eax

    .p2align 4
L(aligned_64_loop):
    movaps  (%eax), %xmm0
    movaps  16(%eax), %xmm1
    movaps  32(%eax), %xmm2
    movaps  48(%eax), %xmm6

    pminub  %xmm1, %xmm0
    pminub  %xmm6, %xmm2
    pminub  %xmm0, %xmm2
    pcmpeqd %xmm3, %xmm2
    pmovmskb %xmm2, %edx
    test    %edx, %edx
    lea 64(%eax), %eax
    jz  L(aligned_64_loop)

    pcmpeqd -64(%eax), %xmm3
    pmovmskb %xmm3, %edx
    test    %edx, %edx
    lea 48(%ecx), %ecx
    jnz L(exit)

    pcmpeqd %xmm1, %xmm3
    pmovmskb %xmm3, %edx
    test    %edx, %edx
    lea -16(%ecx), %ecx
    jnz L(exit)

    pcmpeqd -32(%eax), %xmm3
    pmovmskb %xmm3, %edx
    test    %edx, %edx
    lea -16(%ecx), %ecx
    jnz L(exit)

    pcmpeqd %xmm6, %xmm3
    pmovmskb %xmm3, %edx
    test    %edx, %edx
    lea -16(%ecx), %ecx
    jnz L(exit)

    jmp L(aligned_64_loop)

    .p2align 4
L(exit):
    sub %ecx, %eax
    shr $2, %eax
    test    %dl, %dl
    jz  L(exit_high)

    mov %dl, %cl
    and $15, %cl
    jz  L(exit_1)
    ret

    .p2align 4
L(exit_high):
    mov %dh, %ch
    and $15, %ch
    jz  L(exit_3)
    add $2, %eax
    ret

    .p2align 4
L(exit_1):
    add $1, %eax
    ret

    .p2align 4
L(exit_3):
    add $3, %eax
    ret

    .p2align 4
L(exit_tail0):
    xor %eax, %eax
    ret

    .p2align 4
L(exit_tail1):
    mov $1, %eax
    ret

    .p2align 4
L(exit_tail2):
    mov $2, %eax
    ret

    .p2align 4
L(exit_tail3):
    mov $3, %eax
    ret

    .p2align 4
L(exit_tail4):
    mov $4, %eax
    ret

    .p2align 4
L(exit_tail5):
    mov $5, %eax
    ret

    .p2align 4
L(exit_tail6):
    mov $6, %eax
    ret

    .p2align 4
L(exit_tail7):
    mov $7, %eax
    ret

END (__wcslen_sse2)
#endif

还有个差不多的
/home/feng/glibc_source/sysdeps/x86_64/wcslen.S
都是glibc的代码 .我在 wsl x64 win10 下调试 遇到的是上面的 sse 优化过的汇编。

上一篇下一篇

猜你喜欢

热点阅读