《华为C&C++语言安全规范》笔记_华为 c&c++ 语言安全编程规范-程序员宅基地

技术标签: C\C++  c++  代码规范  c  安全  华为  

《华为C&C++语言安全规范》笔记

通过阅读《华为C&C++语言安全规范》1,我了解到了我在编程中很多缺失的部分。现在记录下几个要点:

规则1.1.4:严禁对指针变量进行sizeof操作

编码人员往往由于粗心,将指针当做数组进行sizeof操作,导致实际的执行结果与预期不符。 下面的代码,buffer和path分别是指针和数组,编码人员想对这2个内存进行清0操作,但由于编码人员的疏忽,第5行代码,将内存大小误写成了sizeof,与预期不符。
如果要判断当前的指针类型大小,请使用sizeof(char *)的方式。

char *buffer = (char *)malloc(size);
char path[MAX_PATH] = {
     0};
...
memset(path, 0, sizeof(path));
memset(buffer, 0, sizeof(buffer));  

相关指南:
CERT.ARR01-C. Do not apply the sizeof operator to a pointer when taking the size of an array

指针与数组名与指针有太多的相似,甚至很多时候,数组名可以作为指针使用。但是数组和指针存在差异。指针,是一个变量,存储的数据是地址。数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组,其外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量2。数组名只是大多数时候隐式转换成指向首元素的指针类型右值。这些时候不会转换:1)对其用 &;2)对其用 sizeof;3)C++中取引用3

我们先来一段代码作为演示:

#include<iostream>
using namespace std;
void func(int C[])
{
    
    cout<<"In function, C sizeof(C):"<<sizeof(C)<<endl;
    cout<<"C point:"<<C<<endl;
    cout<<"C &point:"<<&C<<endl;
    C++;
}
int main()
{
    
    int A[10];
    int* B=new int[10];
    cout<<"A sizeof(A):"<<sizeof(A)<<endl;
    cout<<"B sizeof(B):"<<sizeof(B)<<endl;
    // 取引用地址
    cout<<"A point:"<<A<<endl;
    cout<<"A &point:"<<&A<<endl;
    cout<<"B point:"<<B<<endl;
    cout<<"B &point:"<<&B<<endl;

    //调用函数
    func(A);
    //A++;//Error
    return 0;
}

在X86的编译环境下,输出的结果为:

A sizeof(A):40
B sizeof(B):4
A point:0x6dfec8
A &point:0x6dfec8
B point:0x1fa838
B &point:0x6dfec4
In function, C sizeof(C):4
C point:0x6dfec8
C &point:0x6dfeb0

显然第14行输出的是数组长度,第15行输出的是指针长度(在X86下为4字节,在x64环境下为8字节)。

第17-第20行,对数组取引用,其地址和本身的地址是一样,而指针则不一样。

第23行,当调用函数的时候,数组会转换为指针,因此长度为4,并且可以做自加运算。

规则1.2.1:断言必须使用宏定义,禁止直接调用系统提供的assert()

断言只能在调试版使用,断言被触发后,程序会立即退出,因此严禁在正式发布版本使用断言,请通过编译选项进行控制。 错误用法如:

int Foo(int *array, int size)
{
     
    assert(array != NULL);
    ...
}

ASSERT()是MFC的宏4,ASSERT只有在Debug版本中才有效,如果编译为Release版本则被忽略。
assert()的功能类似,它是ANSI C标准中规定的函数,它与ASSERT的一个重要区别是可以用在Release版本中5

在msvc16里面是这样定义的:

#undef assert

#ifdef NDEBUG

    #define assert(expression) ((void)0)

#else

    _ACRTIMP void __cdecl _wassert(
        _In_z_ wchar_t const* _Message,
        _In_z_ wchar_t const* _File,
        _In_   unsigned       _Line
        );

    #define assert(expression) (void)(                                                       \
            (!!(expression)) ||                                                              \
            (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
        )

#endif

在MinGW里面

/* According to C99 standard (section 7.2) the assert
   macro shall be redefined each time assert.h gets
   included depending on the status of NDEBUG macro.  */
#undef assert
...
#ifdef NDEBUG
#define assert(_Expression) ((void)0)
#else /* !defined (NDEBUG) */
#if defined(_UNICODE) || defined(UNICODE)
#define assert(_Expression) \
 (void) \
 ((!!(_Expression)) || \
  (_wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__),0))
#else /* not unicode */
#define assert(_Expression) \
 (void) \
 ((!!(_Expression)) || \
  (_assert(#_Expression,__FILE__,__LINE__),0))
#endif /* _UNICODE||UNICODE */
#endif /* !defined (NDEBUG) */

综上所述,使用语言自带的assert即可,也没有其他的选择。当在Release环境下,assert也自动编译为((void)0)对于华为的这条安全规范表示不解,希望有人能解答一下。

另外:这里还发现很有意思的东西:

/* According to C99 standard (section 7.2) the assert
macro shall be redefined each time assert.h gets
included depending on the status of NDEBUG macro. */

意思就是每次包含<assert.h>时,都会根据NDEBUG的当前状态重新定义assert宏6

规则1.2.3:严禁在断言内改变运行环境

在程序正式发布阶段,断言不会被编译进去,为了确保调试版和正式版的功能一致性,严禁在断言中使用任何赋值、修改变量、资源操作、内存申请等操作。 例如,以下的断言方式是错误的:

ASSERT(p1 = p2); //p1被修改
ASSERT(i++ > 1000); //i被修改
ASSERT(close(fd) == 0);//fd被关闭

建议1.2.1:不要将多条语句放在同一个断言中

为了更加准确地发现错误的位置,每一条断言只校验一个条件。 下面的断言同时校验多个条件,在断言触发的时
候,无法判断到底是哪一个条件导致的错误:

int Foo(int *array, int size)
{
     
	ASSERT(array != NULL && size > 0 && size < MAX_SIZE);
	...
}

应该将每个条件分开:

int Foo(int *array, int size)
{
     
    ASSERT(array != NULL);
    ASSERT(size > 0);
    ASSERT(size < MAX_SIZE);
    ...
}

规则1.3.2:严禁对公共接口API函数的参数进行ASSERT操作

对于设计成API的函数,必须对参数进行合法性判断,严禁在API实现过程中产生RASH。对API函数的参数进行ASSERT操作是没有意义的。 例如,对于提供应用服务器IP的平台公共API接口这样实现是错误的:

int GetServerIP(char *ip, size_t ipSize)
{
     
    ASSERT(ip != NULL);
    ...
}

公共接口API应当对输入参数进行代码检查:

int GetServerIP(char *ip, size_t ipSize)
{
     
    if (ip == NULL) {
     
    	...
    }
    ...
}

建议1.3.1:谨慎使用不可重入函数

不可重入函数在多线程环境下其执行结果不能达到预期效果,需谨慎使用。常见的不可重入函数包括:
rand, srand
getenv, getenv_s
strtok
strerror
asctime, ctime, localtime, gmtime
setlocale
atomic_init
tmpnam
mbrtoc16, c16rtomb, mbrtoc32, c32rtomb
gethostbyaddr
gethostbyname
inet_ntoa

在这里插入图片描述7

建议1.3.2:字符串或指针作为函数参数时,请检查参数是否为NULL

如果字符串或者指针作为函数参数,为了防止空指针引用错误,在引用前必须确保该参数不为NULL,如果上层调用者已经保证了该参数不可能为NULL,在调用本函数时,在函数开始处可以加ASSERT进行校验。 例如下面的代码,因为BYTE *p有可能为NULL,因此在使用前需要进行判断。

int Foo(int *p, int count)
{
     
    if (p != NULL && count > 0) {
     
        int c = p[0];
    }
    ...
}
int Foo2()
{
     
    int *arr = ...
    int count = ...
    Foo(arr, count);
    ...
}

下面的代码,由于p的合法性由调用者保证,对于Foo函数,不可能出现p为NULL的情况,因此加上ASSERT进行校验。

int Foo(int *p, int count)
{
     
    ASSERT(p != NULL); //ASSERT is added to verify p.
    ASSERT(count > 0);
    int c = p[0];
    	...
    }
int Foo2()
{
     
    int *arr = ...
    int count = ...
    ...
    if (arr != NULL && count > 0) {
     
    	Foo(arr, count);
    }
    ...
}

规则1.5.1:禁用C++异常机制

严禁使用C++的异常机制,所有的错误都应该通过错误值在函数之间传递并做相应的判断, 而不应该通过异常机制进行错误处理。 编码人员必须完全掌控整个编码过程,建立攻击者思维,增强安全编码意识,主动把握有可能出错的环节。而使用C++异常机制进行错误处理,会削弱编码人员的安全意识。 异常机制会打乱程序的正常执行流程,使程序结构更加复杂,原先申请的资源可能会得不到有效清理。 异常机制导致代码的复用性降低,使用了异常机制的代码,不能直接给不使用异常机制的代码复用。 异常机制在实现上依赖于编译器、操作系统、处理器,使用异常机制,导致程序执行性能降低。 在二进制层面,程序被加载后,异常处理函数增加了程序的被攻击面,攻击者可以通过覆盖异常处理函数地址,达到攻击的效果。 例外: 在接管C++语言本身抛出的异常(例如new失败、STL)、第三方库(例如IDL)抛出的异常时,可以使用异常机制,例如:

int len = ...;
char *p = NULL;
try {
     
	p = new char[len];
}
catch (bad_alloc) {
     
    ...
    abort();
}

相关指南:
Google C++ Style Guide.Exceptions: We do not use C++ exceptions.

这点在《游戏引擎架构8》中也提到过。

规则1.6.3:严禁在构造函数中创建线程

构造函数内仅作成员变量的初始化工作,其他的操作通过成员函数完成。

规则1.6.5:如果类的公共接口中返回类的私有数据地址,则必须加const类

实例:

class CMsg {
     
public:
    CMsg();
    ~CMsg();
    Const unsigned char *GetMsg();
protected:
    int size;
    unsigned char *msg;
};
CMsg::CMsg()
{
     
    size = 0;
    msg = NULL;
}
const unsigned char *CMsg::GetMsg()
{
     
	return msg;
}

规则1.7.3:禁用pthread_exit、ExitThread函数

严禁在线程内主动终止自身线程,线程函数在执行完毕后会自动、安全地退出。主动终止自身线程的操作,不仅导致代码复用性变差,同时容易导致资源泄漏错误。

建议1.7.1:禁用exit、ExitProcess函数(main函数除外)

程序应该安全退出,除了main函数以外,禁止任何地方调用exit、ExitProcess函数退出进程。直接退出进程会导致代码的复用性降低,资源得不到有效地清理。 程序应该通过错误值传递的机制进行错误处理。 以下代码加载文件,加载过程中如果出错,直接调用exit退出:

void LoadFile(const char *filePath)
{
     
    FILE* fp = fopen(filePath, "rt");
    if (fp == NULL) {
     
    	exit(0);
    }
    ...
}

正确的做法应该通过错误值传递机制,例如:

BOOL LoadFile(const char *filePath)
{
     
    BOOL ret = FALSE;
    FILE* fp = fopen(filePath, "rt");
    if (fp != NULL) {
     
    	...
    }
    ...
    return ret;
}

建议1.7.2:禁用abort函数

abort会导致程序立即退出,资源得不到清理。 例外: 只有发生致命错误,程序无法继续执行的时候,在错误处理函数中使用abort退出程序,例如:

void FatalError(int sig)
{
     
	abort();
}
int main(int argc, char *argv[])
{
     
       signal(SIGSEGV, FatalError);
       ...
}

规则2.5:调用格式化函数时,禁止format参数由外部可控

调用格式化函数时,如果format参数由外部可控,会造成字符串格式化漏洞。 这些格式化函数有: 格式化输出函数:xxxprintf 格式化输入函数:xxxscanf 格式化错误消息函数:err(),verr(),errx(),verrx(),warn(),vwarn(),warnx(),vwarnx(),error(),error_at_line(); 格式化日志函数:syslog(),vsyslog()。
错误示例:

char *msg = GetMsg();
...
printf(msg);

推荐做法:

char *msg = GetMsg();
...
printf("%s\n", msg);

相关指南:
CERT.FIO47-C. Use valid format strings
MITRE.CWE-134: Use of Externally-Controlled Format String

规则4.2:整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值

由于整数在运算过程中可能溢出,当运算结果赋值给比他更大类型,或者和比他更大类型进行比较时,会导致实际结果与预期结果不符。 请观察以下二个代码及其输出:

int main(int argc, char *argv[])
{
     
    unsigned int a = 0x10000000;
    unsigned long long b = a * 0xab;
    printf("b = %llX\n", b);
    return 0;
}

输出: b = B0000000

int main(int argc, char *argv[])
{
     
    unsigned int a = 0x10000000;
    unsigned long long b = (unsigned long long )a * 0xab;
    printf("b = %llX\n", b);
    return 0;
}

输出: b = AB0000000

规则4.3:禁止对有符号整数进行位操作符运算

位操作符(~、>>、<<、&、^、|)应该只用于无符号整型操作数。 错误示例:

int data = ReadByte();
int a = data >> 24;

推荐做法:(为简化示例代码,此处假设ReadByte函数实际不存在返回值小于0的情况)

unsigned int data = (unsigned int)ReadByte();
unsigned int a = data >> 24;

相关指南:
CERT.INT13-C. Use bitwise operators only on unsigned operands
MISRA.C.2004.Rule 12.7 (required): Bitwise operators shall not be applied to operands whose underlyingtype is signed.

在C语言中,如果在未知的有符号上执行位操作,很可能会导致缓冲区溢出,从而在某些情况下导致攻击者执行任意代码,同时,还可能会出现出乎意料的行为或编译器定义的行为。

代码如下:

#include<stdio.h>
int main()
{
    
    int y=0x80000000;
	printf("%d\n",y>>24);//以十进制有符号形式输出。
	printf("%u\n",y>>24);//以十进制无符号形式输出。
    return 0;
}

输出为:

-128
4294967168

由于int类型的最高位是符号位,剩下的31位才用来存储数值,y>>24的结果应该是0xffffff80(负数右移,左补1),当我们以无符号整型输出时,为正数的0xffffff80,以有符号整型输出时,应减一再取反,结果为-1289

然而,在右移运算中,空出的位用 0 还是符号位进行填充是由于具体的 C 语言编译器实现来决定。在通常情况下,如果要进行移位的操作数是无符号类型的,那么空出的位将用 0 进行填充;如果要进行移位的操作数是有符号类型的,则 C 语言编译器实现既可选择 0 来进行填充,也可选择符号位进行填充。

因此,如果很关心一个右移运算中的空位,那么可以使用 unsigned 修饰符来声明变量,这样空位都会被设置为 0。同时,如果一个程序采用了有符号数的右移位操作,那么它就是不可移植的10

规则4.4:禁止整数与指针间的互相转化

指针的大小随着平台的不同而不同,强行进行整数与指针间的互相转化,降低了程序的兼容性,在转换过程中可能引起指针高位信息的丢失。
错误示例:

char *ptr = ...;
unsigned int number = (unsigned int)ptr;

推荐做法:

char *ptr = ...;
uintptr_t number = (uintptr_t)ptr;

相关指南:
CERT.INT36-C. Converting a pointer to integer or integer to pointer
MISRA.C.2004.Rule 11.3 (advisory): A cast should not be performed between a pointer type and an integral type.

规则4.5:禁止对指针进行逻辑或位运算(&&、||、!、~、>>、<<、&、^、|)

对指针进行逻辑运算,会导致指针的性质改变,可能产生内存非法访问的问题。 下面是错误的用法:

BOOL dealName(const char *nameA, const char *nameB)
{
     
    ...
    if (nameA)
    ...
    if (!nameB)
    ...
}

下面是正确的用法:

BOOL dealName(const char *nameA, const char *nameB)
{
     
    ...
    if (nameA != NULL)
    ...
    if (nameB == NULL)
    ...
}

例外: 为检查地址对齐而对地址指针进行的位运算可以作为例外。
相关指南:
MISRA.C.2004.Rule 12.6 (advisory): The operands of logical operators (&&, || and !) should be effectively Boolean. Expressions that are effectively Boolean should not be used as operands to operators other than (&&, ||, !, =, ==, != and ?.

规则5.1:内存申请前,必须对申请内存大小进行合法性校验

内存申请的大小可能来自于外部数据,必须检查其合法性,防止过多地、非法地申请内存。不能申请0长度的内存。 例如:

int Foo(int size)
{
     
    if (size <= 0) {
     
    //error
    ...
    }
    ...
    char *msg = (char *)malloc(size);
    ...
}

相关指南:
CERT.MEM04-C. Beware of zero-length allocations
MITRE.CWE-789: Uncontrolled Memory Allocation

规则5.2:内存分配后必须判断是否成功

char *msg = (char *)malloc(size);
if (msg != NULL) {
     
	...
}

相关指南:
CERT.MEM11-C. Do not assume infinite heap space
CERT.ERR33-C. Detect and handle standard library errors
CERT.MEM52-CPP. Detect and handle memory allocation errors
MITRE.CWE 252, Unchecked Return Value
MITRE.CWE 391, Unchecked Error Condition
MITRE.CWE 476, NULL Pointer Dereference
MITRE.CWE 690, Unchecked Return Value to NULL Pointer Dereference
MITRE.CWE 703, Improper Check or Handling of Exceptional Conditions
MITRE.CWE 754, Improper Check for Unusual or Exceptional Conditions

规则5.3:禁止引用未初始化的内存

malloc、new分配出来的内存没有被初始化为0,要确保内存被引用前是被初始化的。 以下代码使用malloc申请内存,在使用前没有初始化:

int *CalcMetrixColomn( int **metrix ,int *param, size_t size )
{
     
    int *result = NULL;
    ...
    size_t bufSize = size * sizeof(int);
    ...
    result = (int *)malloc(bufSize);
    ...
    result[0] += metrix[0][0] * param[0];
    ...
    return result;
}

以下代码使用memset_s()对分配出来的内存清零。

int *CalcMetrixColomn(int **metrix ,int *param, size_t size)
{
     
    int *result = NULL;
    ...
    size_t bufSize = size * sizeof(int);
    ...
    result = (int *)malloc(bufSize);
    ...
    int ret = memset_s(result, bufSize, 0, bufSize); //【修改】确保内存被初始化后才被引用
    ...
    result[0] += metrix[0][0] * param[0];
    ...
    return result;
}

相关指南:
CERT.EXP33-C. Do not read uninitialized memory
CERT.EXP53-CPP. Do not read uninitialized memory

规则5.4:内存释放之后立即赋予新值

悬挂指针可能会导致双重释放(double-free)以及访问已释放内存的危险。消除悬挂指针以及消除众多与内存相关危险的一个最为有效地方法就是当指针使用完后将其置新值。 如果一个指针释放后能够马上离开作用域,因为它已经不能被再次访问,因此可以无需对其赋予新值。
示例:

char *message = NULL;
    ...
    message = (char *)malloc(len);
    ...
    if (...) {
     
        free(message); //在这个分支内对内存进行了释放
        message = NULL; //释放后将指针赋值为NULL
    }
    ...
    if (message != NULL) {
     
        free(message);
        message = NULL;
}

相关指南:
CERT.MEM01-C. Store a new value in pointers immediately after free()
CERT.MEM50-CPP. Do not access freed memory

规则5.5:禁止使用realloc()函数

realloc()原型如下:

void *realloc(void *ptr, size_t size);

随着参数的不同,其行为也是不同。 1) 当ptr不为NULL,且size不为0时,该函数会重新调整内存大小,并将新的内存指针返回,并保证最小的size的内容不变; 2) 参数ptr为NULL,但size不为0,那么行为等同于malloc(size); 3) 参数size为0,则realloc的行为等同于free(ptr)。 由此可见,一个简单的C函数,却被赋予了3种行为,这不是一个设计良好的函数。虽然在编码中提供了一些便利性,但是却极易引发各种bug。
相关指南:
CERT.MEM36-C. Do not modify the alignment of objects by calling realloc()

规则5.6:禁止使用alloca()函数申请栈上内存

POSIX和C99均未定义alloca()的行为,在有些平台下不支持该函数,使用alloca会降低程序的兼容性和可移植性,该函数在栈帧里申请内存,申请的大小很可能超过栈的边界,影响后续的代码执行。 请使用malloc或new,从堆中动态分配内存。

规则7.1:创建文件时必须显式指定合适的文件访问权限

创建文件时,如果不显式指定合适访问权限,可能会让未经授权的用户访问该文件。 下列代码没有显式配置文件的访问权限。

int fd = open(fileName, O_CREAT | O_WRONLY); //【错误】缺少访问权限设置

推荐做法:

int fd = open(fileName, O_CREAT | O_WRONLY, S_IRUSR|S_IWUSR);

规则8.3:严禁使用string类存储敏感信息

string类是C++内部定义的字符串管理类,如果口令等敏感信息通过string进行操作,在程序运行过程中,敏感信息可能会散落到内存的各个地方,并且无法清0。 以下代码,Foo函数中获取密码,保存到string变量password中,随后传递给VerifyPassword函数,在这个过程中,password实际上在内存中出现了2份。

int VerifyPassword(string password)
{
     
    //...
}
int Foo()
{
     
    string password = GetPassword();
    VerifyPassword(password);
    ...
}

应该使用char或unsigned char保存敏感信息,如下代码:

int VerifyPassword(const char *password)
{
     
    //...
}
int Foo()
{
     
    char password[MAX_PASSWORD] = {
     0};
    GetPassword(password, sizeof(password));
    VerifyPassword(password);
    ...
}

  1. 《华为C&C++语言安全规范》 https://www.jb51.net/books/720904.html

  2. C/C++数组名与指针区别深入探索_ljob2006的博客-程序员宅基地 https://blog.csdn.net/ljob2006/article/details/4872167

  3. c中,数组名跟指针有区别吗? - 知乎 https://www.zhihu.com/question/41805285

  4. 大小写 ASSERT 有什么区别_百度知道 https://zhidao.baidu.com/question/110720542.html

  5. 问题: 什么是ASSERT()? ASSERT()和assert()的区别是什么?_流风的专栏-程序员宅基地 https://blog.csdn.net/procedurecode/article/details/1942115

  6. 第一章assert.h_暮秋小屋-程序员宅基地 https://blog.csdn.net/qq_49150256/article/details/112753240

  7. 可重入函数对于线程安全的意义(附函数表) - 云+社区 - 腾讯云 https://cloud.tencent.com/developer/article/1685935

  8. 游戏引擎架构 (豆瓣) https://book.douban.com/subject/25815142/

  9. 有符号执行位操作导致的BUG_Tonson_的博客-程序员宅基地 https://blog.csdn.net/weixin_44444450/article/details/109668780

  10. 位操作及其使用注意事项,C语言位操作及其使用方法详解 http://c.biancheng.net/view/362.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/z736248591/article/details/117767444

智能推荐

JavaScript语法高亮库highlight.js使用_语法高亮js css文件-程序员宅基地

文章浏览阅读9.9k次。highlight.js是一款基于JavaScript的语法高亮库,目前支持125种编程语言,有63种可供选择的样式,而且能够做到语言自动识别,和目前主流的JS框架都能兼容,可以混合使用。这款高亮库可以用在博客系统中,其使用方法及其简单,几乎不需要任何学习成本,下面介绍highlight.js的使用。1.获取highlight.js库,用户可以从官网获取:地址:https://highlightjs_语法高亮js css文件

【笔记】strftime的使用方法-程序员宅基地

文章浏览阅读5.1k次。strftimestrftime是C语言标准库中用来格式化输出时间的的函数。下面是strftime的用法各参数意义代码使用示例#include<stdio.h>#include<time.h>#define print(s1, s2,s3) \ printf("%-20s%-30s%s\n",s1, s2,s3);int main(){ time_t rawtime; struct tm* timeinfo; char timE[80]; /

2018.09.12 poj3621Sightseeing Cows(01分数规划+spfa判环)-程序员宅基地

文章浏览阅读147次。传送门 01分数规划板题啊。 发现就是一个最优比率环。 这个直接二分+spfa判负环就行了。 代码:#include&lt;iostream&gt;#include&lt;cstdio&gt;#include&lt;cstring&gt;#include&lt;algorithm&gt;#include&lt;cmath&gt;#define N 1005#define...

hive sql的常用日期处理函数总结_hive sql 日期函数-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏14次。1)date_format函数(根据格式整理日期)  作用:把一个字符串日期格式化为指定的格式。select date_format('2017-01-01','yyyy-MM-dd HH:mm:ss'); --日期字符串必须满足yyyy-MM-dd格式   结果:2017-01-01 00:00:002)date_add、date_sub函数(加减日期)  作用:把一个字符串日期格式加一天、减一天。select date_add('2019-01-01',1); ..._hive sql 日期函数

Android Studio使用百度语音合成是TTS时报错: ****.so文件找不到的有关问题_旧版的百度语言合成报错-程序员宅基地

文章浏览阅读2.1k次。使用百度语音合成过程时,一直error : notfint libgnustl_shared.so在项目工程gradle文件中添加如下代码段:sourceSets { main { jniLibs.srcDirs = ['libs'] } }..._旧版的百度语言合成报错

BZOJ1202: [HNOI2005]狡猾的商人_狡猾的商人[hnoi2005]-程序员宅基地

文章浏览阅读425次。Description 刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。账本上记录了n个月以来的收入情况,其中第i个月的收入额为Ai(i=1,2,3…n-1,n), 。当 Ai大于0时表示这个月盈利Ai 元,当 Ai小于0时表示这个月亏损Ai元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。 刁姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那_狡猾的商人[hnoi2005]

随便推点

kali 安装取证工具volatility_kali安装volatility-程序员宅基地

文章浏览阅读3.1k次,点赞2次,收藏12次。计算机取证 volatility_kali安装volatility

html禁止图片缓存(刷新网站)_html禁止浏览器缓存图片-程序员宅基地

文章浏览阅读2.1k次。地址:https://blog.csdn.net/fareast_mzh/article/details/81464031_html禁止浏览器缓存图片

如何设置一个计算机用户访问磁盘,登录后限制用户访问硬盘分区-程序员宅基地

文章浏览阅读1.3k次。限制用户登录后访问硬盘分区。我们的部门有一台公用计算机,该计算机由我维护。其他同事也可以偶尔使用它。我在操作系统中为自己创建了一个超级管理员用户,还创建了一个受限用户。登录到计算机后,如何允许受限用户查看但不能访问用于存储重要文件的D分区?您可以通过以下操作实现该目标:在系统桌面上使用鼠标依次选择“开始”。在弹出窗口的“打开”(Open)字段中键入gpedit.msc,然后单击“确定”(OK)按钮..._win7 分区只能某个用户打开

更改vscode Java项目的.class文件输出路径_vscode怎么class文件-程序员宅基地

文章浏览阅读6.7k次,点赞17次,收藏21次。1.在vscode里面按下快捷键ctrl+shift+p2.输入Classpath3.点击Output下的Browse选择.class文件的输出路径4.如图,选择完以后,.class文件的输出层级目录会自动建立_vscode怎么class文件

Python缩进规则-程序员宅基地

文章浏览阅读1.2w次,点赞4次,收藏24次。python的缩进规则:对于类定义、函数定义、流程控制语句、异常处理语句等,行尾的冒号和下一行的缩进,表示下一个代码块的开始,而缩进的结束则表示此代码块的结束。通常情况下都是采用4个空格长度作为一个缩进量(一个Tab键就表示4个空格)。一,Python缩进长度及缩进字符。 看到网上一些Python缩进的错误示范,“tab符和空格不能混用”,“缩进一定是4个空格”下列演示。​def change(a): print(id(a)) # 指向的是同一个对象(tab缩进) a=10_python缩进规则

微信小程序api视频课程-定时器-setTimeout的使用_微信小程序 settimeout 向上层传值-程序员宅基地

文章浏览阅读1.1k次。JS代码 /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { setTimeout( function(){ wx.showToast({ title: '黄菊华老师', }) },2000 ) },说明该代码只执行一次..._微信小程序 settimeout 向上层传值

推荐文章

热门文章

相关标签