UINT64整型数据在格式化时使用了不匹配的格式化符%d导致其他参数无法打印的问题排查

news2024/11/15 16:47:03

目录

1、问题描述

2、格式化函数内部解析待格式化参数的完整机制说明

2.1、传递给被调用函数的参数是通过栈传递的

2.2、格式化函数是如何从栈上找到待格式化的参数值,并完成格式化的?

2.3、字符串格式化符%s对应的异常问题场景说明

2.4、为了方便理解上述机制,附上VC6.0中的CString类的Format函数的实现源码

2.5、如果要格式化某个C++类对象的数据,且对象中包含多个数据成员,要明确指定要格式化的那个数据成员

3、本案例中的问题分析与排查

3.1、问题代码

3.2、初步分析

3.3、为什么UINT64型数据使用%d格式化符会有问题?

3.4、解决办法

4、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       同事在分析软件的运行日志排查业务问题时,发现某个关键数据的值没有打印出来,于是查看打印日志的代码,但并没有找到问题。同事找到我,希望我去帮忙分析一下,看看是什么原因导致的。经深入分析发现,这个问题和格式化函数内部从栈内存上解析传进来的参数数据时出现了异常,代码中对待格式化的UINT64整型数据错误地使用了%d格式化符引发的。今天我们就来详细讲述一下这个问题的排查过程。

1、问题描述

       在源码中找到对应的那句打印日志的代码,如下所示:

现将要打印的信息格式化到一个字符串中,然后将字符串打印出来。上述代码中打印了5个参数(strId.c_str()),这些参数格式化到目标字符串中,查看日志发现第5个参数并没有打印出来。其中,第1个参数(strId.c_str())是字符串首地址,第2个参数(tRtcPlayItem.play_idx()函数的返回值)是UINT64整数值,第3个参数和第4个参数都是bool型,第5个参数(strId.c_str())也是字符串的首地址。第5个参数是字符串的首地址,其指向的内存中是有有效的字符串的。

       一般这类数据格式化问题,一般都是待格式化数据和对应的格式化符不匹配导致的,一般会导致两类问题。一是打印出来的数据有异常或者数据打印不出来,二是格式化函数内部从栈上获取参数数据时访问了不该访问的内存,触发内存访问违例,引发崩溃。

       大家基本都知道格式化符与待格式化参数类型不匹配时,可能会出问题或者引发软件异常崩溃,但为啥会出问题以及出问题的根本原因,估计没多少人知道!本文就带着大家去详细探究深层次的根本原因,掌握本文的内容,下次大家再遇到格式化问题时,就能自行去分析排查了!

2、格式化函数内部解析待格式化参数的完整机制说明

2.1、传递给被调用函数的参数是通过栈传递的

      一般在调用函数时,传递给被调用函数的参数,是压到栈上传递给被调用函数的,即传入参数通过栈传递给被调用函数的。这点通过汇编代码看的很清楚,比如如下的代码:(调用实现将两个int型数据相加的AddNum函数)

如上所示,在调用AddNum之前,将要传入的参数a和b分别压到栈上,然后再调用AddNum函数。对于被调用函数AddNum,则到栈上去获取传入的参数值。

当然,参数通过栈传递不是绝对的,比如对于64位程序,可用的寄存器比较多,要格式化的参数不多时,可能直接使用寄存器传递,这样效率比较高,比压入栈内存及读取栈内存,要快很多。下面一段话摘自微软官方说明:

在 ARM 和 x64 处理器上,接受 __cdecl、__stdcall等调用约定,但编译器一般会忽略它。 按照 ARM 和 x64 上的约定,自变量将尽可能传入寄存器,后续自变量传递到堆栈中。

       为什么要讲参数是如何传递给被调用函数的呢?这个和格式化函数如何从栈上获取传入的参数内容有直接的关系。要理解格式化函数内部是如何从栈上读取传入的参数,必须了解函数调用时的栈分布(在调用函数前要把参数值压到栈上,通过栈传递给被调用函数)。

2.2、格式化函数是如何从栈上找到待格式化的参数值,并完成格式化的?

       对于支持可变参的格式化函数,他没法确定主调函数实际传入了多少个参数,只能依次根据设置的格式化符依次到栈上读取对应的数据。要搞清楚格式化时为什么会出问题(甚至是崩溃),必须要搞清楚格式化函数如何根据格式化符从栈上依次解析出待格式化的参数值的。搞清楚这个问题,还需要了解上面讲到的函数调用时参数是怎么压到栈上的,多个参数时的压栈顺序则是和函数的调用约定有关系的。

       以格式化函数sprintf为例,将两个int型变量的值格式化到字符串中:

int i = 2, j= 3;
char szBuffer[128] = { 0 };
sprintf( szBuffer, "i = %d, j =%d", i, j );

其中,格式化函数sprintf的声明为:

int __cdecl sprintf( char *buffer, const char *format, ... );

该函数是C运行时库中提供的系统函数,支持可变参的,可以对多个参数进行格式化。

对于可变参的函数,其调用约定一般都为__cdecl(C调用),不能声明为__stdcall,因为对于可变参函数,函数主调方才知道传入了多少个参数,只有函数主调方去才知道释放该释放多少参数占用的栈内存,所以要声明为__cdecl调用,而对于__stdcall调用,是被调函数去释放传入参数占用的栈内存的。通过这个支持可变参的函数,就能辅助记忆__cdecl调用和__stdcall调用的区别,这是个小技巧。

       此外,对于__cdecl调用,参数是从右向左依次压栈的,即先将参数j的值压栈,然后再将参数i的值压栈。对于常用的__stdcall标准调用,参数也是从右到左依次入栈的。

       之前我们看过VC6.0自带的CString类的支持可变参数格式化的Format函数的内部代码实现,从这些代码中就能看出格式化函数内部是如何根据格式化符依次到栈上找到对应的带格式化数据的。

       格式化函数不管你传入了多少个参数,它是根据设置的格式化符(主要关注格式化符类型与个数),从左到右依次解析出设置的格式化符,然后根据格式化符到栈上去找待格式化的数据的。格式化函数能获取到传入的参数占用的栈内存的首地址,所有要传入的参数是依次压到栈上,所以这些参数占用的栈内存是连续的、挨在一起的。

       还是以上面的例子为例,讲解格式化函数内部大概是如何根据格式化符去获取要格式化的数据!首先,绘制出了函数调用之前压栈的栈分布图,如下所示:

当解析到第1个格式化符%d,就到栈内存上取4个字节的内存,然后将内存中的值读出来,作为要格式化的内容。解析完第1个%d,将保存参数占用的栈内存首地址的指针p向后偏移%d对应的数据占用的4字节,为解析下一个待格式化的参数做准备。

       当解析到第2个%d时,再到指针p中保存的内存起始地址处取出4个字节内存,将这4个字节内存中的内容作为要格式化的数据写到目标串中。同样地,将保存参数占用的栈内存首地址的指针p向后偏移%d对应的数据占用的4字节,为解析下一个待格式化的参数做准备。

2.3、字符串格式化符%s对应的异常问题场景说明

       如果格式化符是%s,对应的是字符串,则调用函数前对应的参数压到栈上的内容是字符串的首地址,这样在处理到%s时从栈上将调用函数前压入的字符串首地址读出来,然后到这个首地址对应的内存中将字符串读出来。

       如果格式化符与带格式化参数类型不匹配,根据%s到栈上取出的字符串首地址是0x00000001这样的很小的地址,然后去访问这个很小地址,就会触发内存访问违例。我们多次讲过,在Windows系统中,地址值小于64KB的内存区域是NULL地址内存区,是禁止访问的,一旦访问就会触发内存访问违例,系统会强行将进程终止掉。这个问题场景,我们以前就遇到过。

2.4、为了方便理解上述机制,附上VC6.0中的CString类的Format函数的实现源码

       支持可变参数格式化的函数有很多,比如printf和sprintf等,但这些C运行时函数无法在Visual Studio中看到其内部源码实现。以前我们从VC6.0 MFC库中抠出CString类的完整实现源码,为了方便大家理解上面讲解的格式化函数内部解析机制,这个地方我们给出CString::Format相关代码:

// formatting (using wsprintf style formatting)
void CString::Format(LPCTSTR lpszFormat, ...)
{
    assert(IsValidString(lpszFormat));

    va_list argList;
    va_start(argList, lpszFormat);
    FormatV(lpszFormat, argList);
    va_end(argList);
}

#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap)        ((void)(ap = (va_list)0))

#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end
#define va_copy(destination, source) ((destination) = (source))


void CUIString::FormatV(LPCTSTR lpszFormat, va_list argList)
{
    assert(IsValidString(lpszFormat));

    va_list argListSave = argList;

    // make a guess at the maximum length of the resulting string
    int nMaxLen = 0;
    for (LPCTSTR lpsz = lpszFormat; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
    {
        // handle '%' character, but watch out for '%%'
        if (*lpsz != '%' || *(lpsz = _tcsinc(lpsz)) == '%')
        {
            nMaxLen += _tclen(lpsz);
            continue;
        }

        int nItemLen = 0;

        // handle '%' character with format
        int nWidth = 0;
        for (; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
        {
            // check for valid flags
            if (*lpsz == '#')
                nMaxLen += 2;   // for '0x'
            else if (*lpsz == '*')
                nWidth = va_arg(argList, int);
            else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' ||
                *lpsz == ' ')
                ;
            else // hit non-flag character
                break;
        }
        // get width and skip it
        if (nWidth == 0)
        {
            // width indicated by
            nWidth = _ttoi(lpsz);
            for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
                ;
        }
        assert(nWidth >= 0);

        int nPrecision = 0;
        if (*lpsz == '.')
        {
            // skip past '.' separator (width.precision)
            lpsz = _tcsinc(lpsz);

            // get precision and skip it
            if (*lpsz == '*')
            {
                nPrecision = va_arg(argList, int);
                lpsz = _tcsinc(lpsz);
            }
            else
            {
                nPrecision = _ttoi(lpsz);
                for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
                    ;
            }
            assert(nPrecision >= 0);
        }

        // should be on type modifier or specifier
        int nModifier = 0;
        if (_tcsncmp(lpsz, _T("I64"), 3) == 0)
        {
            lpsz += 3;
            nModifier = FORCE_INT64;
#if !defined(_X86_) && !defined(_ALPHA_)
            // __int64 is only available on X86 and ALPHA platforms
            assert(FALSE);
#endif
        }
        else
        {
            switch (*lpsz)
            {
                // modifiers that affect size
            case 'h':
                nModifier = FORCE_ANSI;
                lpsz = _tcsinc(lpsz);
                break;
            case 'l':
                nModifier = FORCE_UNICODE;
                lpsz = _tcsinc(lpsz);
                break;

                // modifiers that do not affect size
            case 'F':
            case 'N':
            case 'L':
                lpsz = _tcsinc(lpsz);
                break;
            }
        }

        // now should be on specifier
        switch (*lpsz | nModifier)
        {
            // single characters
        case 'c':
        case 'C':
            nItemLen = 2;
            va_arg(argList, TCHAR_ARG);
            break;
        case 'c'|FORCE_ANSI:
        case 'C'|FORCE_ANSI:
            nItemLen = 2;
            va_arg(argList, CHAR_ARG);
            break;
        case 'c'|FORCE_UNICODE:
        case 'C'|FORCE_UNICODE:
            nItemLen = 2;
            va_arg(argList, WCHAR_ARG);
            break;

            // strings
        case 's':
            {
                LPCTSTR pstrNextArg = va_arg(argList, LPCTSTR);
                if (pstrNextArg == NULL)
                    nItemLen = 6;  // "(null)"
                else
                {
                    nItemLen = lstrlen(pstrNextArg);
                    nItemLen = max(1, nItemLen);
                }
            }
            break;

        case 'S':
            {
#ifndef _UNICODE
                LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
                if (pstrNextArg == NULL)
                    nItemLen = 6;  // "(null)"
                else
                {
                    nItemLen = wcslen(pstrNextArg);
                    nItemLen = max(1, nItemLen);
                }
#else
                LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
                if (pstrNextArg == NULL)
                    nItemLen = 6; // "(null)"
                else
                {
                    nItemLen = lstrlenA(pstrNextArg);
                    nItemLen = max(1, nItemLen);
                }
#endif
            }
            break;

        case 's'|FORCE_ANSI:
        case 'S'|FORCE_ANSI:
            {
                LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
                if (pstrNextArg == NULL)
                    nItemLen = 6; // "(null)"
                else
                {
                    nItemLen = lstrlenA(pstrNextArg);
                    nItemLen = max(1, nItemLen);
                }
            }
            break;

        case 's'|FORCE_UNICODE:
        case 'S'|FORCE_UNICODE:
            {
                LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
                if (pstrNextArg == NULL)
                    nItemLen = 6; // "(null)"
                else
                {
                    nItemLen = wcslen(pstrNextArg);
                    nItemLen = max(1, nItemLen);
                }
            }
            break;
        }

        // adjust nItemLen for strings
        if (nItemLen != 0)
        {
            if (nPrecision != 0)
                nItemLen = min(nItemLen, nPrecision);
            nItemLen = max(nItemLen, nWidth);
        }
        else
        {
            switch (*lpsz)
            {
                // integers
            case 'd':
            case 'i':
            case 'u':
            case 'x':
            case 'X':
            case 'o':
                if (nModifier & FORCE_INT64)
                    va_arg(argList, __int64);
                else
                    va_arg(argList, int);
                nItemLen = 32;
                nItemLen = max(nItemLen, nWidth+nPrecision);
                break;

            case 'e':
            case 'g':
            case 'G':
                va_arg(argList, DOUBLE_ARG);
                nItemLen = 128;
                nItemLen = max(nItemLen, nWidth+nPrecision);
                break;

            case 'f':
                {
                    double f;
                    LPTSTR pszTemp;

                    // 312 == strlen("-1+(309 zeroes).")
                    // 309 zeroes == max precision of a double
                    // 6 == adjustment in case precision is not specified,
                    //   which means that the precision defaults to 6
                    pszTemp = (LPTSTR)_alloca(max(nWidth, 312+nPrecision+6));

                    f = va_arg(argList, double);
                    _stprintf( pszTemp, _T( "%*.*f" ), nWidth, nPrecision+6, f );
                    nItemLen = _tcslen(pszTemp);
                }
                break;

            case 'p':
                va_arg(argList, void*);
                nItemLen = 32;
                nItemLen = max(nItemLen, nWidth+nPrecision);
                break;

                // no output
            case 'n':
                va_arg(argList, int*);
                break;

            default:
                assert(FALSE);  // unknown formatting option
            }
        }

        // adjust nMaxLen for output nItemLen
        nMaxLen += nItemLen;
    }

    GetBuffer(nMaxLen);
    //VERIFY(_vstprintf(m_pchData, lpszFormat, argListSave) <= GetAllocLength());

    // 将上一句的VERIFY代码注释掉,重新写
    // 此处去掉了VERIFY
    int nWriteCount = GetAllocLength() + 1;
    int nLen = _vstprintf(m_pchData, nWriteCount, lpszFormat, argListSave);
    assert( nLen <= GetAllocLength() );

    ReleaseBuffer();

    va_end(argListSave);
}

上述代码中涉及到两个宏:__crt_va_arg_INTSIZEOF,这两个宏很有特点,实现的很巧妙,要搞懂代码,需要先看懂这两个宏的含义。_INTSIZEOF宏实现4字节对齐。__crt_va_arg宏则实现栈内存地址指针ap向后累加偏移,同时返回当前待格式化参数的栈内存地址。

有人可能会说,为什么要看古老的VC6.0中的CString中的代码,为啥不看新版本Visual Studio中的CString类的实现源码呢?新版本的Visual Studio中的CString类都是用模版实现的,看着很费劲,VC6.0中的代码看着比较直观易懂,主要为了搞清楚格式化函数内部的解析机制,只要能搞懂这个机制和解析过程即可分析问题了。

2.5、如果要格式化某个C++类对象的数据,且对象中包含多个数据成员,要明确指定要格式化的那个数据成员

       以duilib开源库中的CStdString字符串类为例,类中包含了两个数据成员,如下:

class DIRECTUI_API CStdString
{
public:
    enum { MAX_LOCAL_STRING_LEN = 16 };

    CStdString();                                    
    CStdString(const TCHAR ch);

    LPCTSTR GetData(){ return m_pstr;}

    // ......  // 其他成员函数省略

protected:
    LPTSTR m_pstr;                           // 字符指针
    TCHAR m_szBuffer[MAX_LOCAL_STRING_LEN];  // 字符缓冲区
};

在格式化时必须明确格式化的是哪个数据成员,不能直接把C++类对象作为参数传到函数中。因为如果传入的参数是个类对象,会把整个类的数据成员值都压到栈上去的,但实际上我们只是想格式化类中的某个数据成员,多压入的数据就会导致后续的格式化符处理出问题。比如有问题的代码如下:

CStdString strId;
CStdString strName;

// 中间对strId和strName的赋值代码省略,假设这两个变量中已经存放了真实的字符串数据。
// ...

CStdString strLog;
strLog.Format(_T("strId: %s, strName: %s"), strId, strName);

此处的写法就是有问题的,直接把类对象strId和strName作为参数传进去了,而对应的CStdString包含了两个数据成员m_pstr和m_szBuffer,这样在调函函数之前压栈时将两个成员变量都压到栈上了。而实际上我们要格式化的是m_pstr。当然这个地方有一点比较迷惑人,CStdString就是处理字符串的字符串类,大家在格式化时理所当然直接传入对象进去。

       正确的做法是,调用CStdString::GetData接口获取到成员变量m_pstr,我们只需要格式化m_pstr成员的数据,正确的代码如下:

CStdString strId;
CStdString strName;

// 中间对strId和strName的赋值代码省略,假设这两个变量中已经存放了真实的字符串数据。
// ...

CStdString strLog;
strLog.Format(_T("strId: %s, strName: %s"), strId.GetData(), strName.GetData() );

3、本案例中的问题分析与排查

3.1、问题代码

       出问题的打印日志代码如下所示:

第1个参数是字符串首地址,第2个参数是整数值,第3个参数和第4个参数都是bool型,第5个参数也是字符串的首地址。一般这类问题,都是格式化符和待格式化参数类型不匹配不一致引发的,但并没有发现明显的不匹配问题。

       以前我在做C++软件调试与异常排查培训课时,讲到过格式化符与待格式化参数不匹配的问题,维护这块代码的同事怀疑是不是和压栈的参数有关系。确实从汇编上看,在调用函数时,对于C调用约定,参数要从右到左依次压栈,被调用函数内部从栈上读取传入的参数值。

       对于支持可变参的格式化函数,函数内部时根据设置的格式化符,依次到栈上取出对应的要格式化的数据。如果格式化符与待格式化的参数可不一致,则在从栈上读取数据时会出现地址偏差,读取了不该读取的内存区域,导致出异常。

3.2、初步分析

       最开始怀疑第3个和第4个bool型参数,都使用了%d格式化符,是不是有问题?bool型变量好像只压栈一个字节的内存数据,但格式化函数内部遇到%d格式化符时,会从栈上取出4个字节,这个好像不一致了,于是在bool型待格式化参数前人为加上一个int型的强转,编译代码测试了一下,第5个参数还是打印不出来。

       其实待格式化的bool型参数,在压栈的时候压入的四个字节,即四字节对齐。于是询问了同事,第2个参数类型是啥,这个参数是一个函数的返回值,不问还好,一问就不好了,这个参数类型是UINT64,即无符号64位整型,结果格式化符使用的是%d,问题就出在这里了,应该使用64位无符号整型对应的格式化符%lld,用%d肯定是有问题的。

3.3、为什么UINT64型数据使用%d格式化符会有问题?

       格式化函数内部根据当前要的格式化符,到栈内存上读取对应字节数的内存中的数据,作为当前要格式化的数据,同时将栈内存地址向后偏移当前格式化符对应的内存长度,为处理下一个格式化符数据的格式化做准备。然后取出第二个格式化符,处理第二个待格式化的参数。然后依次类推!

       本问题中出问题的打印代码如下:

上述出问题的那行代码,传入了5个参数,在调用打印函数之前需要把这5个参数压到栈上,传递给被调用的日志打印函数。这几个参数压入栈后的栈分布图如下:

在本问题中,处理%d格式化符对应的UINT64数据格式化时,因为格式化符%d对应4个字节,所以只从栈内存上取出4字节内存中的数据(取得不是64为整型数据对应的8字节),同时栈内存指针向后偏移%d格式化符对应的4字节,这样导致在解析第3个%d格式化符时取的还是UINT64整型数据的一部分,即错位了,所以出问题了!所以继续往下推算,在解析第5个%s格式化符对应的内存时取的是第4个bool型的内存,把bool值作为一个字符串的首地址,然后取读取地址中的字符串,因为地址很小,应该会触发内存访问违例,产生崩溃。但实际运行时并没有崩溃,并且输出了(null)值,难道是格式化函数中特别做了一个保护,发现地址为空,直接输出了一个null串?估计是这样的。

3.4、解决办法

        解决这个问题的办法很简单,只要将UINT64类型参数对应的格式化符换成%lld就可以了。用一句话概括,要使用带格式化参数类型对应长度的格式化符!

4、最后

       本案例很典型,通过这个案例详细讲解了格式化函数内部机制和解析过程,对于排查数据格式化问题(比如数据没有打印出来,或者程序发生异常崩溃)有直接的指导价值,有直接的实战参考价值。参考这个实例的分析过程,大家后面遇到格式化问题时,就能自行去分析和排查了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1019329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

node 之 express 框架(初级)

一、express 热更新 1、安装扩展 npm install node-dev -D2、在根目录下的 package.json 文件中进行配置 3、之后的启动执行下面的命令即可 npm run dev二、mvc中的 模板引擎 1、ejs模板引擎的安装 npm install ejs -s2、在根目录下的app.js文件中配置 app.set(view engin…

我学编程全靠B站了,真香(第一期)

你好&#xff0c;我是Martin。 我是就读于B站大学2020届的Martin同学&#xff0c;反正我学习计算机真的是全靠 B 站了。 我是个刷视频狂魔&#xff0c;B站收藏夹里也收藏了很多编程类视频&#xff0c; 比如C/C、Go语言、操作系统、数据结构和算法、计算机网络、数据库、Pyth…

深入了解Python运算符和表达式:从基础到高级

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 Python运算符和表达式是…

JavaScript 学习笔记(基础)

其是一门跨平台、面向对象的脚本语言&#xff08;直译型语言&#xff09;&#xff0c;用来控制网页行为&#xff0c;能使网页产生交互效果&#xff01;下面以 JS 代称 JavaScript 引入HTML结构文件有两类方式&#xff1a; 内部脚本 行联式嵌入式外部脚本* 基本语法&#xff1…

npm发布vue3自定义组件库--方法二

npm发布vue3自定义组件库 创建项目 vue create test-ui自定义组件 创建自定义组件&#xff0c;组件名称根据你的需求来&#xff0c;最好一个组件一个文件夹&#xff0c;下图是我的示例。 src/components 组件和你写页面一样&#xff0c;所谓组件就是方便实用&#xff0c;不…

NotePad++ 在行前/行后添加特殊字符内容方法

我们在处理数据时&#xff0c;会遇到需要在每行数据前面、后面、开头、结尾添加各种不一样的字符 如果数据不多&#xff0c;我们可以自己手动的去添加&#xff0c;但如果达到了成百上千行&#xff0c;此时再机械的手动添加是不现实的 这里教给大家如何快速的在数据每行的前后…

华为云云耀云服务器L实例评测|cento7.9在线使用cloudShell下载rpm解压包安装mysql并开启远程访问

文章目录 ⭐前言⭐使用华为cloudShell连接远程服务器&#x1f496; 进入华为云耀服务器控制台&#x1f496; 选择cloudShell ⭐安装mysql压缩包&#x1f496; wget下载&#x1f496; tar解压&#x1f496; 安装步骤&#x1f496; 初始化数据库&#x1f496; 修改密码&#x1f4…

JavaCTF记录

Springmvcdemo 在没有提升权限之前&#xff0c;整个环境只有Cookie是可控的&#xff0c;并且提升权限也是要通过cookie来&#xff0c;先看看它对cookie做了什么&#xff0c;看一下过滤器 public void doFilter(ServletRequest request, ServletResponse response, FilterChai…

Python实现猎人猎物优化算法(HPO)优化随机森林回归模型(RandomForestRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

Pyhton压缩JS代码

文章目录 1.安装依赖2.目录结构3.代码4.执行结果 1.安装依赖 pip install jsmin2.目录结构 3.代码 import jsmindef run(src_path, tgt_path):with open(src_path, "r", encodingutf-8) as input_file:with open(tgt_path, "w", encodingutf-8) as outpu…

外贸型CRM软件系统的作用

外贸企业在国际市场上面临着大量的竞争和风险&#xff0c;需要不断创新发展&#xff0c;提高自身的竞争力&#xff0c;但又受制于客户管理、业务效率、数据利用和风险控制等方面的不足。为了解决外贸企业面临的问题和挑战&#xff0c;外贸CRM系统应运而生。那么&#xff0c;什么…

面试(架构,网络)

java八股 treemap和linkdedhashmap区别&#xff0c;实现原理 https://blog.csdn.net/shidebin/article/details/126814905 架构 https://www.cnblogs.com/crazymakercircle/p/17197091.htmlhttps://www.cnblogs.com/crazymakercircle/p/17197091.html 羊了个羊https://www.c…

【LeetCode-简单题】1047. 删除字符串中的所有相邻重复项

文章目录 题目方法一&#xff1a;利用栈做匹配方法二&#xff1a;消消乐 题目 方法一&#xff1a;利用栈做匹配 class Solution {public String removeDuplicates(String s) {Deque<Character> deque new LinkedList<>();StringBuffer str new StringBuffer();fo…

封装七牛云存储工具类

文章目录 封装七牛云存储工具类&#xff08;为啥选择七牛云&#xff1f;当然是因为它能免费使用喽&#xff01;&#xff01;&#xff01;白嫖怪哈哈哈&#xff01;&#xff01;&#xff01;&#xff09;图片存储方案Java SDK操作七牛云封装工具类 封装七牛云存储工具类&#xf…

如何在 Excel 中求平方根

需要在 Excel 中求一个数字的平方根吗&#xff1f;使用几个内置的 Excel 函数和公式可以轻松计算平方根。在本分步指南中&#xff0c;您将学习在 Excel 中计算平方根的 5 种不同方法&#xff0c;包括使用 SQRT 函数、POWER 函数、指数公式、VBA 代码和 Power Query。跟随教程&a…

我学编程全靠B站了,真香-国外篇(第三期)

你好&#xff0c;我是Martin。 今天来点猛料&#xff0c;给大家推荐点我的压箱收藏-国外知名大学的公开课。 我推荐的不多&#xff0c;本着少就是多的原则&#xff0c;只给大家推荐我看过最好的五门视频&#xff0c;主要是来自两所国外高校&#xff1a;MIT美国麻省理工、CMU卡…

Pytorch实现MNIST字符识别

1.下载mnist.pkl.gz 网址&#xff1a;http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz 数据集文件夹路径是data2/mnist/mnist.pkl.gz 2.读取数据 from pathlib import Pathimport matplotlib.pyplot as pltDATA_PATHPath("./data2") PATHDATA_P…

无涯教程-JavaScript - LOG函数

描述 LOG函数将数字的对数返回您指定的基数。 语法 LOG (number, [base])争论 Argument描述Required/OptionalNumberThe positive real number for which you want the logarithm.RequiredBaseThe base of the logarithm. If base is omitted, it is assumed to be 10.Opti…

Linux底层基础知识

一.汇编&#xff0c;C语言&#xff0c;C&#xff0c;JAVA之间的关系 汇编&#xff0c;C语言&#xff0c;C可以通过不同的编译器&#xff0c;编译成机器码。而java只能由Java虚拟机识别。Java虚拟机可以看成一个操作系统&#xff0c;Java虚拟机是由汇编&#xff0c;C&#xff0c…

KVM嵌套虚拟化实现

KVM嵌套虚拟化实现 理论 Libvirt主要支持三种 CPU mode host-passthrough: libvirt 令 KVM 把宿主机的 CPU 指令集全部透传给虚拟机。因此虚拟机能够最大限度的使用宿主机 CPU 指令集&#xff0c;故性能是最好的。但是在热迁移时&#xff0c;它要求目的节点的 CPU 和源节点的…