当前位置: 首页 > 编程日记 > 正文

C和C++安全编码笔记:整数安全

5.1 整数安全导论:整数由包括0的自然数(0, 1, 2, 3, …)和非零自然数的负数(-1, -2, -3, …)构成。

5.2 整数数据类型:整数类型提供了整数数学集合的一个有限子集的模型。一个具有整数类型的对象的值是附着在这个对象上的数学值。一个具有整数类型的对象的值的表示方式(representation)是在为该对象分配的存储空间中该值的特定位模式编码。

在C中每个整数类型的对象需要一个固定的存储字节数。<limits.h>头文件中的常量表达式CHAR_BIT,给出了一个字节中的位数,它必须至少为8,但可能会更大,这取决于具体的实现。除unsigned char型外,不是所有的位都必须用来表示值,未使用的位被称为填充(padding)。

标准的整数类型由一组有符号的整数类型和相应的无符号整数类型组成。

无符号整数类型:C要求无符号整数类型值使用无偏移的纯二进制系统表示。无符号整数是计数器的自然选择。标准的无符号整数类型(按照它们的长度非递减排序)是:unsigned char、unsigned short int、unsigned int、unsigned long int、unsigned long long int,关键字int可以省略,除非它是唯一存在的整数类型的关键字。

特定于编译器和平台的整数极值记录在<limits.h>头文件中。牢记这些值都是特定于平台的。出于可移植性的考虑,在代码中应该使用具名常量而不是实际的值

回绕:涉及无符号操作数的计算永远不会溢出,因为不能用结果为无符号整数类型表示的结果值被该类型可以表示的最大值加1之和取模减(reduced modulo)。因为回绕,一个无符号整数表达式永远无法求出小于零的值。

// 回绕:涉及无符号操作数的计算永远不会溢出
void test_integer_security_wrap_around()
{unsigned int ui = UINT_MAX; fprintf(stdout, "ui value 1: %u\n", ui); // 4294967295ui++; fprintf(stdout, "ui value 2: %u\n", ui); // 0ui = 0; fprintf(stdout, "ui value 3: %u\n", ui); // 0ui--; fprintf(stdout, "ui value 4: %u\n", ui); // 4294967295//for (unsigned i = n; --i >= 0; ) // 此循环将永远不会终止unsigned int i = 0, j = 0, sum = 0;// ... 对i, j, sum进行一些赋值运算操作if (sum + i > UINT_MAX) { } // 不会发生,因为sum+i回绕了if (i > UINT_MAX - sum) { } // 好很多if (sum - j < 0) { } // 不会发生,因为sum-j回绕了if (j > sum) { } // 正确
}

除非使用<stdint.h>中明确指定宽度(exact-width)的类型,否则回绕使用的宽度取决于实现,这意味着结果会因平台而异。

有符号整数类型:有符号整数用于表示正值和负值,其值的范围取决于为该类型分配的位数及其表示方式。在C中,除了_Bool类型以外,每种无符号类型都有一种对应的占用相同存储空间的有符号类型。标准的有符号整数类型(按照长度非递减排序,例如,long long int不能短于long int)包括如下类型:signed char、short int、int、long int、long long int,除了char类型,signed可以忽略(无修饰的char的表现要么如同unsigned char,要么如同signed char,这取决于实现,并且出于历史原因,它被视为一个单独的类型)。int可以省略,除非它是唯一存在的关键字。

所有足够小的非负值在对应的有符号和无符号类型中有同样的表示方式。一个称为符号位的位被当作最高位,用于指示所表示的值是否为负。C标准允许的负数表示方法有三种,分别是原码表示法(sign and magnitude)、反码表示法(one’s complement)和补码表示法(two’s complement)

(1).原码表示法:符号位表示值为负(符号位设置为1)还是为正(符号位设置为0),其它值位(非填充)表示以纯二进制表示法(与无符号类型相同)表示的该值的幅值。若要取一个原码的相反数,只要改变符号位。例如,在纯二进制表示法或原码中,二进制数0000101011等于十进制数43,要取该值的相反数,只要设置符号位:二进制数1000101011等于十进制数-43。

(2).反码表示法:符号位具有权数-(2^(N-1) - 1),其它值位的权数与无符号类型相同。例如,在反码中,二进制数1111010100等于十进制数-43。假定宽度是10位,符号位具有权数-(2^9 - 1)即-511,其余位等于468,于是468-511=-43。若要取一个反码的相反数,需要改变每一位(包括符号位)

(3).补码表示法:符号位具有权数-(2^(N-1)),其它值位的权数与无符号类型相同。例如,在补码中,二进制数1111010101等于十进制数-43.假定宽度是10位,符号位具有权数-(2^9)即-512,其余位等于469,于是469-512=-43.若要取一个补码的相反数,首先构造反码的相反数,然后再加1(在需要时进位)

对于数学值0,原码和反码都有两种表示方式:正常0和负0(negative zero)。逻辑操作可能产生负0,但任何算术操作都不允许结果是负0,除非其中一个操作数具有一个负0表示方式。下表展示了假定在10位宽并忽略填充位时,一些有趣值的原码、反码和补码表示:在使用补码表示法的计算机上,有符号整数的取值范围是-2^(N-1) ~ 2^(N-1) - 1。当使用反码表示法和原码表示法时,其取值范围的下界变成-2^(N-1) + 1,而上界则保持不变。

有符号整数的取值范围:下表中的”最小值”列确定每个标准有符号整数类型保证的可移植范围。这些幅值被实现定义的具有相同符号的幅值所取代,如那些为x86-32架构所示的幅值。C标准要求标准有符号类型的最小宽度分别是:signed char(8)、short(16)、int(16)、long(32)、long long(64)。一个给定实现的实际宽度可以用<limits.h>中定义的最大可表示值作参考。这些类型对象的大小(存储的字节数)可以由sizeof(typename)确定,这个大小包含填充位(如果有的话)。一个整数类型的最小值和最大值取决于该类型的表示方式、符号性和宽度。

整数溢出:当一个有符号整数运算的结果值不能用结果类型表示时就会发生溢出。在C中有符号整数溢出是未定义的行为,从而允许实现默默地回绕(最常见的行为)、陷阱,或两者兼而有之。用补码表示的一个给定类型最小负值的相反数不能以那种类型表示

// 有符号整数溢出
void test_integer_security_overflow()
{int i = INT_MAX; // 2147483647, int最大值i++; fprintf(stdout, "i = %d\n", i); // -2147483648, int最小值i = INT_MIN; // -2147483648, int最小值i--; fprintf(stdout, "i = %d\n", i); // 2147483647, int最大值std::cout << "abs(INT_MIN): " << std::abs(INT_MIN) << std::endl; // -2147483648// 因为二进制补码表示是不对称的,数值0被表示为”正”数,所以用补码表示的一个给定类型最小负值的相反数不能以那种类型表示// 对最小的负值而言,结果是未定义的或错误的#define abs(n) ((n) < 0 ? -(n) : (n))#undef abs
}

字符类型:在char型用于数值时仅使用明确的signed char或unsigned char。建议仅使用signed char和unsigned char类型存储和使用小数值(也就是范围分别在SCHAR_MIN和SCHAR_MAX之间,或0和UCHAR_MAX之间的值),因为这是可移植的保证数据的符号字符类型的唯一方式。平凡的char不应该被用来存储数值,因为编译器有定义char的自由,使其要么与signed char,要么与unsigned char具有相同的范围、表示和行为。

// 字符类型
void test_integer_security_char()
{
{// char类型的变量c可能是有符号或无符号的// 初始值200(它具有signed char类型)无法在(有符号的)char类型中表示(这是未定义的行为)// 许多编译器将用标准的由无符号转换到有符号的模字大小(modulo-word-size)规则把200转换为-56char c = 200;int i = 1000;fprintf(stdout, "i/c = %d\n", i / c); // 在windows/linux上会输出-17, 1000/-56=-17
}{// 声明unsigned char型变量c,使后面的除法操作与char的符号性无关,因此它有一个可预见的结果unsigned char c = 200;int i = 1000;fprintf(stdout, "i/c = %d\n", i / c); // 5
}
}

数据模型:对于一个给定的编译器,数据模型定义了为标准数据类型分配的大小。这些数据模型通常使用一个XXXn的模式命名,其中每个X都指一个C类型,而n指的是大小(通常为32或64),通常命名为:ILP64:int、long和指针类型是64位宽;LP32:long和指针是32位宽。

其它整数类型:C也在标准头文件<stdint.h>、<stdtypes.h>和<stddef.h>中定义了其它整数类型。这些类型包括扩展的整数类型(extended integer type),它们是可选的、由实现定义的、完全支持的扩展,与标准的整数类型一起,组成整数类型的一般类。标准头文件中诸如whatever_t定义的标识符都是typedef(类型定义),也就是说,它们是现有类型的同义词,而不是新类型。

size_t:是无符号整数类型的sizeof运算符的结果,它在标准头文件<stddef.h>中被定义。size_t类型的变量保证有足够的精度来表示一个对象的大小。size_t的最大值由SIZE_MAX宏指定。

ptrdiff_t:是一种有符号整数类型,它表示两个指针相减的结果,并被定义在标准头文件<stddef.h>中。当两个指针相减时,其结果是两个数组元素的下标之差。其结果的大小是实现定义的,且它的类型(一种有符号整数类型)是ptrdiff_t。ptrdiff_t的下限和上限分别由PRTDIFF_MIN和PTRDIFF_MAX定义。

void test_integer_security_ptrdiff_t()
{int i = 5, j = 6;typedef int T;T *p = &i, *q = &j;ptrdiff_t d = p - q;fprintf(stdout, "pointer diff: %lld\n", d);fprintf(stdout, "sizeof(ptrdiff_t): %d\n", sizeof(ptrdiff_t)); // 8
}

intmax_t和uintmax_t:是具有最大宽度的整数类型,它们可以表示任何其它具有相同符号性的整数类型所能表示的任何值,允许在程序员定义的整数类型(相同符号性)与intmax_t和uintmax_t类型之间进行转换。

void test_integer_security_intmax_t()
{typedef unsigned long long mytypedef_t; // 假设mytypedef_t是个128位的无符号整数,其实它并不是fprintf(stdout, "mytypedef_t length: %d\n", sizeof(mytypedef_t));mytypedef_t x = 0xffff;uintmax_t temp;temp = x; // 始终是安全的mytypedef_t x2 = 0xffffffffffffffff;fprintf(stdout, "x2: %ju\n", (uintmax_t)x2); // 将保证打印正确的x2值,无论它的长度是多少
}

格式化I/O函数可用于输入和输出最大宽度的整数类型值。在格式字符串中的j长度修饰符表明以下d、i、o、u、x、X或n转换说明符将适用于一个类型为intmax_t或unitmax_t的参数。

intptr_t和uintptr_t:C标准不保证存在一个整数类型,它大到足以容纳一个指向对象的指针。然而,如果确实存在这样的类型,那么它的有符号版本称为intptr_t,它的无符号版本称为uintptr_t。这些类型的算术运算并不保证产生一个有用的值。

独立于平台的控制宽度的整数类型:C语言在头文件<stdint.h>和<inttypes.h>中引入了整数类型,它为程序员提供typedef以便他们更好地控制宽度。这些整数类型是实现定义的,并包括以下几种类型:

(1).int#_t、uint#_t:其中#代表一个确切的宽度,如int8_t、uint32_t。

(2).int_least#_t、uint_least#_t:其中#代表宽度值,如int_least32_t、uint_least16_t.

(3).int_fast#_t、uint_fast#_t:其中#代表最快的整数类型宽度的值,如int_fast16_t、uint_fast64_t。

头文件<stdint.h>还为扩展类型定义了表示相应的最大值(对于有符号类型,还有最小值)的常数宏。

特定于平台的整数类型:除了在C标准中定义的整数类型,供应商通常还定义了特定于平台的整数类型。例如,Microsoft Windows API定义了大量的整数类型,包括__int8、__int16、BOOL、CHAR、LONG64等。

5.3 整数转换

转换整数:转换是一种用于表示赋值、类型强制转换或者计算的结果值的底层数据类型的改变。从具有某个宽度的类型向一种具有更大宽度的类型转换,通常会保留数学值。然而,相反方向的转换很容易导致高位的损失(涉及有符号整数类型时甚至会更糟),除非该值的幅值一直足够小,可以被正确地表示。转换是强制转换时显式发生的或作为一个操作的需要而隐式发生的。虽然隐式转换简化了编程,但也可能会导致数据丢失或错误解释。

C标准规定了C编译器应该如何处理转换操作,包括:整数类型提升(integer promotion)、整数转换级别(integer conversion rank)以及普通算术转换(usual arithmetic conversion)。

整数转换级别:每一种整数类型都有一个相应的整数转换级别,它决定了转换操作将会如何执行。下面列出了C标准定义的用于决定整数转换级别的规则:

(1).没有任何两种不同的有符号整数类型具有相同的级别,即使它们的表示法相同。

(2).有符号整数类型的级别比任何精度比它低的有符号整数类型的级别高。

(3).long long int类型的级别比long int高;long int的级别比int高;int的级别比short int高;short int的级别比signed char高。

(4).无符号整数类型的级别与对应的有符号整数类型的级别相同(如果相应的有符号整数类型存在的话)。

(5).标准整数类型的级别高于具有同样宽度的扩展整数类型的级别。

(6)._Bool类型的级别应当低于所有其它标准整数类型。

(7).char、signed char和unsigned char三种类型的级别相同。

(8).与”其它具有相同精度的扩展有符号整数类型”相关的任何扩展有符号整数类型的级别由具体实现定义,但它们仍然要遵从用于决定整数转换级别的其它规则。

(9).对T1、T2、T3三种整数类型,如果T1的级别比T2高,T2的级别又比T3高,那么T1的级别也比T3高。

C标准建议用于size_t和ptrdiff_t类型的整数转换级别不应高于signed long int,除非该实现支持足够大的对象使得这成为必要。

整数类型提升:如果一个整数类型具有低于或等于int或unsigned int的整数转换级别,那么它的对象或表达式在用于一个需要int或unsigned int的表达式时,就会被提升。整数类型提升被作为普通算术转换的一个组成部分。

void test_integer_security_promotion()
{
{int sum = 0;char c1 = 'a', c2 = 'b';// 整数类型提升规则要求把c1和c2都提升到int类型// 然后把这两个int类型的数据相加,得到一个int类型的值,并且该结果被保存在整数类型变量sum中sum = c1 + c2;fprintf(stdout, "sum: %d\n", sum); // 195
}{signed char cresult, c1, c2, c3;c1 = 100; c2 = 3; c3 = 4;// 在用8位补码表示signed char的平台上,c1与c2相乘的结果可能会因超过这些平台上signed char类型的最大值(+127)// 而引起signed char类型的溢出.然而,由于发生了整数类型提升,c1, c2和c3都被转换为int,因此整个表达式的结果// 能够被成功地计算出来.该结果随后被截断,并被存储在cresult中.由于结果位于signed char类型的取值范围内,因// 此该截断操作并不会导致数据丢失或数据解释错误 cresult = c1 * c2 / c3;fprintf(stdout, "cresult: %d\n", cresult); // 75
}{unsigned char uc = UCHAR_MAX; // 0xFF// 当uc用作求反运算符"~"的操作数时,通过使用零扩展把它扩展为32位,它被提升为signed int类型,因此,在// x86-32架构平台中,该操作始终产生一个类型为signed int的负值int i = ~uc;fprintf(stdout, "i: %0x\n", i); // 0xffffff00
}
}

整数提升保留值,其中包括符号。如果在所有的原始值中,较小的类型可以被表示为一个int,那么:原始值较小的类型会被转换成int;否则,它被转换成unsigned int

之所以需要整数类型提升,主要是为了防止运算过程中中间结果发生溢出而导致算术错误,也为了在该架构中以自然的大小执行操作

普通算术转换:是一套规则。一致性转换涉及不同类型的两个操作数。其中一个操作数或者两个操作数都可能被转换。很多接受整数操作数的运算符都采用普通算术转换(usual arithmetic conversion)对其操作数进行转换。这些运算符包括*、/、%、+、-、<、>、<=、>=、==、!=、&、^、|和条件运算符(?:)。当整数类型提升规则被同时应用到两个操作数之后,以下规则会被应用到已提升的操作数上:

(1).如果两个操作数具有相同的类型,则不需要进一步的转换。

(2).如果两个操作数拥有相同的整数类型(有符号或无符号),具有较低整数转换级别的类型的操作数会被转换到拥有较高级别的操作数的类型。例如,如果一个signed int操作数和一个signed long操作数并列,那么signed int操作数被转换为signed long。

(3).如果无符号整数类型操作数的级别大于或等于另一个操作数类型的级别,则有符号整数类型操作数将被转换为无符号整数类型操作数的类型。例如,如果一个signed int操作数和一个unsigned int操作数并列,那么signed int操作数将转换为unsigned int。

(4).如果有符号整数类型操作数类型能够表示无符号整数类型操作数类型的所有可能值,则无符号整数类型操作数将被转换为有符号整数类型操作数的类型。例如,如果一个64位补码signed long操作数和一个32补码unsigned int操作数并列,那么unsigned int操作数将转换为signed long。

(5).否则,两个操作数都将转换为与有符号整数类型操作数类型相对应的无符号整数类型。

由无符号整数类型转换:从较小的无符号整数类型转换到较大的无符号整数类型始终是安全的,通常通过对其值进行零扩展(zero-extending)而完成。当表达式包含不同宽度的无符号整数操作数时,C标准要求每个操作的结果都具有其中较宽的操作数的类型(和表示范围)。假设相应的数学运算产生一个在结果类型能表示的范围内的结果,则得到的表示值就是那个数学值。如果数学结果值不能用结果类型表示,发生的情况有两类:无符号,损失精度;无符号值转换成有符号值:

void test_integer_security_unsigned_conversion()
{
{ // 无符号,损失精度unsigned int ui = 300;// 当uc被赋予存储在ui中的值时,值300以模2^8取余,或300-256=44unsigned char uc = ui;fprintf(stdout, "uc: %u\n", uc); // 44
}{ // 无符号值转换成有符号值unsigned long int ul = ULONG_MAX;signed char sc;sc = ul; // 可能会导致截断错误fprintf(stdout, "sc: %d\n", sc); // -1
}{ // 当从一个无符号类型转换为有符号类型时,应验证范围unsigned long int ul = ULONG_MAX;signed char sc;if (ul <= SCHAR_MAX) {sc = (signed char)ul; // 使用强制转换来消除警告} else { // 处理错误情况fprintf(stderr, "fail\n");}
}
}

(1).无符号,损失精度:仅对无符号整数类型而言,C规定:值是以模2^w(type)取余,其中2^w(type)是比可以用结果类型表示的最大值大1的数。把一个无符号整数类型的值转换为较窄的宽度的值被良好地定义为以较窄的宽度为模取余。这是通过截断较大值并保留其低位实现的。如果该值不能在新的类型中表示,那么数据就会丢失。当一个值不能在新的类型中表示时,任何大小的有符号和无符号整数类型之间发生的转换都可能会导致数据丢失或错误解释。

(2).无符号值转换成有符号值:当一个大的无符号值转换成宽度相同的有符号类型时,C标准规定,当起始值不能在新的(有符号)类型中表示时:结果是由实现定义的,或发出一个实现定义的信号。从一个无符号的类型转换为有符号类型时,可能发生类型范围错误,包括损失数据(截断)和损失符号(符号错误)。当把一个大的无符号整数转换为一个较小的有符号整数类型时,值会被截断,且最高位变成符号位。由此产生的值可能是负的或正的,这取决于截断后的高位值。如果该值不能在新的类型中表示,数据就会丢失(或错误解释)。当从一个无符号类型转换为有符号类型时,应验证范围

下表总结了x86-32架构中无符号整数类型的转换:

由有符号整数类型转换:从较小的有符号整数类型转换为较大的有符号整数类型始终是安全的,并可以采用对该值进行符号扩展的方法在补码表示中实现:

void test_integer_security_signed_conversion()
{
{ // 有符号,损失精度signed long int sl = LONG_MAX;signed char sc = (signed char)sl; // 强制转换消除了警告fprintf(stdout, "sc: %d\n", sc); // -1
}{ // 当从一个有符号类型转换到精度较低的有符号类型时,应验证范围signed long int sl = LONG_MAX;signed char sc;if ((sl < SCHAR_MIN) || (sl > SCHAR_MAX)) { // 处理错误情况fprintf(stderr, "fail\n");} else {sc = (signed char)sl; // 使用强制转换来消除警告fprintf(stdout, "sc: %d\n", sc);}
}{ // 负值和无符号值的比较固有问题unsigned int ui = UINT_MAX;signed char c = -1;// 由于整数提升,c被转换为unsigned int类型的值0xFFFFFFFF,即4294967295if (c == ui) {fprintf(stderr, "why is -1 = 4294967295\n");}
}{ // 从有符号类型转换为无符号类型时,可能发生类型范围错误,包括数据丢失(截断)和损失符号(符号错误)signed int si = INT_MIN;// 导致损失符号unsigned int ui = (unsigned int)si; // 强制转换消除了警告fprintf(stderr, "ui: %u\n", ui); // 2147483648
}{ // 从有符号类型转换为无符号类型时,应验证取值范围signed int si = INT_MIN;unsigned int ui;if (si < 0) { // 处理错误情况fprintf(stderr, "fail\n");} else {ui = (unsigned int)si; // 强制转换消除了警告fprintf(stdout, "ui: %u\n", ui);}
}
}

(1).有符号,损失精度:把有符号整数类型的值转换为更窄宽度的结果是实现定义的,或者可能引发一个实现定义的信号。一个常见的实现是截断成较小者的尺寸。在这种情况下,所得到的值可能是负的或正的,视截断后的高位值而定。如果该值不能在新的类型中表示,那么数据将会丢失(或错误解释)。当从一个有符号类型转换到精度较低的有符号类型时,应验证范围。从较高精度的有符号类型转换为较低精度的有符号类型需要同时对上限和下限进行检查

(2).从有符号转换到无符号:当有符号和无符号整数类型混合操作时,由普通算术转换确定常见的类型,这个类型至少将具有所涉及的类型中最宽的宽度。C要求如果数学的结果能够用那个宽度表示,那么会产生该值。当将一个有符号整数类型转换为无符号整数类型时,反复加上或减去新类型的宽度(2^N)会使结果落在能够表示的范围内。当把一个有符号整数的值转换为一个宽度相等或更大的无符号整数的值并且有符号整数的值不为负时,该值是不变的。

当将一个有符号整数类型转换为一个宽度相等的无符号整数类型时,不会丢失任何数据,因为保留了位模式。然而,高位失去了它的符号位功能。如果有符号整数的值不为负,则该值不变。如果该值为负,则得到的无符号的值被求值为一个大的有符号整数。如果有符号的值是-2,那么相应的无符号的int值是UINT_MAX-1。从有符号类型转换为无符号类型时,应验证取值范围

下表总结了x86-32平台上有符号整数类型的转换:

转换的影响:隐式转换简化了C语言编程。然而,转换存在潜在的数据丢失或错误解释问题。需避免导致下列结果的转换:(1).损失值:转换为值的大小不能表示的一种类型;(2).损失符号:从有符号类型转换为无符号类型,导致损失符号。

唯一的对所有数据值和所有符号标准的实现都保证安全的整数类型转换是转换为符号相同而宽度更宽的类型

5.4 整数操作:可能会导致异常情况下的错误,如溢出、回绕和截断。当某个操作产生的结果不能在操作结果类型中表示时,就会发生异常情况。下表表示执行整数操作时可能的异常情况,不包括在操作数统一到常见的类型时应用普通算术转换所造成的错误:

赋值:在简单的赋值(=)中,右操作数的值被转换为赋值表达式的类型并替换存储在左操作数所指定的对象的值。用一个有符号整数为一个无符号整数赋值,或者用一个无符号整数为一个宽度相等的有符号整数赋值,都可能导致所产生的值被误解。当从一个具有较大宽度的类型向较小宽度的类型赋值或强制类型转换时,就会导致发生截断。如果该值不能用结果类型表示,那么数据可能会丢失。

int f_5_4(void) { return 66; }
void test_integer_security_assignment()
{
{char c;// 函数f_5_4返回的int值可能在存储到char时被截断,然后在比较之前将其转换回int宽度// 在"普通"char具有与unsigned char相同的取值范围的实现中,转换的结果不能为负,所以下面比较的操作数// 永远无法比较为相等,因此,为了有充分的可移植性,变量c应声明为int类型if ((c = f_5_4()) == -1) {}
}{char c = 'a';int i = 1;long l;// i的值被转换为c=i赋值表达式的类型,那就是char类型,然后包含在括号中的表达式的值被转换为括号外的赋值// 表达式的类型,即long int型.如果i的值不在char的取值范围内,那么在这一系列的分配后,比较表达式// l == i是不会为真的l = (c = i);
}{// 用一个有符号整数为一个无符号整数赋值,或者用一个无符号整数为一个宽度相等的有符号整数赋值,// 都可能导致所产生的值被误解int si = -3;// 因为新的类型是无符号的,那么通过反复增加或减去比新的类型可以表示的最大值大1的数,该值可以被转换,// 直到该值落在新的类型的取值范围内.如果作为无符号值访问,结果值会被误解为一个大的正值unsigned int ui = si;fprintf(stdout, "ui = %u\n", ui); // 4294967293fprintf(stdout, "ui = %d\n", ui); // -3// 在大多数实现中,通过逆向操作可以轻易地恢复原来的值si = ui;fprintf(stdout, "si = %d\n", si); // -3
}{unsigned char sum, c1, c2;c1 = 200; c2 = 90;// c1和c2相加产生的值在unsigned char的取值范围之外,把结果赋值给sum时会被截断sum = c1 + c2;fprintf(stdout, "sum = %u\n", sum); // 34
}
}

加法:可以用来将两个算术操作数或者将一个指针与一个整数相加。如果两个操作数都是算术类型,那么将会对它们执行普通算术转换。二元的”+”运算符的结果就是其操作数的和。递增与加1等价。如果表达式是将一个整数类型加到一个指针上,那么其结果将是一个指针,这称为指针算术运算。两个整数相加的结果总是能够用比两个操作数中较大者的宽度大1位的数来表示。任何整数操作的结果都可以用任何比其中较大者的宽度大1的类型表示。如果结果整数类型占用的位数不足以表示其结果,那么整数加法就会导致溢出或回绕。

void test_integer_security_add()
{
{ // 先验条件测试,补码表示: 用来检测有符号溢出,该解决方案只适用于使用补码表示的架构signed int si1, si2, sum;si1 = -40; si2 = 30;unsigned int usum = (unsigned int)si1 + si2;fprintf(stdout, "usm = %x, si1 = %x, si2 = %x, int_min = %x\n", usum, si1, si2, INT_MIN);// 异或可以被当作一个按位的"不等"操作,由于只关心符号位置,因此把表达式用INT_MIN进行掩码,// 这使得只有符号位被设置if ((usum ^ si1) & (usum ^ si2) & INT_MIN) { // 处理错误情况fprintf(stderr, "fail\n");} else {sum = si1 + si2;fprintf(stdout, "sum = %d\n", sum);}
}{ // 一般的先验条件测试signed int si1, si2, sum;si1 = -40; si2 = 30;if ((si2 > 0 && si1 > INT_MAX - si2) || (si2 < 0 && si1 < INT_MIN - si2)) { // 处理错误情况fprintf(stderr, "fail\n");} else {sum = si1 + si2;fprintf(stdout, "sum = %d\n", sum);	}
}{ // 先验条件测试:保证没有回绕的可能性unsigned int ui1, ui2, usum;ui1 = 10; ui2 = 20;if (UINT_MAX - ui1 < ui2) { // 处理错误情况fprintf(stderr, "fail\n");} else {usum = ui1 + ui2;fprintf(stdout, "usum = %u\n", usum);}
}{ // 后验条件测试unsigned int ui1, ui2, usum;ui1 = 10; ui2 = 20;usum = ui1 + ui2;if (usum < ui1) { // 处理错误情况fprintf(stderr, "fail\n");}
}
}

避免或检测加法产生的有符号溢出:C中有符号溢出是未定义的行为,允许实现默默地回绕(最常见的行为)、陷阱、饱和(固定在最大值/最小值中),或执行实现选择的其它任何行为。

从一个更大的类型向下强制转换:宽度为w的任意两个有符号的整数值真正的和始终可以用w+1位表示。因此,在另外一个宽度更大的类型中执行加法将始终成功。可以对由此产生的值进行范围检查,然后再向下强制转换到原来的类型。一般来说,这种解决方案是依赖于实现的,因为C标准并不能保证任何一个标准的整数类型比另一个整理类型大。

避免或检测加法造成的回绕:对两个无符号的值相加时,如果操作数之和大于结果类型所能存储的最大值,就会发生回绕。虽然无符号整数回绕在C标准中被良好地定义为取模行为,但意外的回绕已导致众多的软件漏洞。

后验条件测试:在操作被执行后进行,它测试操作所得到的值,以确定它是否在有效的范围内。如果一个异常情况可能会导致显然有效的值,那么这种做法是无效的,然而,无符号加法始终可以用于测试回绕。

减法:与加法类型,减法也是一种加法操作。对减法而言,两个操作数都必须是算术类型或指向兼容对象类型的指针。从一个指针中减去一个整数也是合法的。递减操作等价于减1操作。如果两个操作之差是负数,那么无符号减法会产生回绕。

void test_integer_security_substruction()
{
{ // 先验条件测试:两个正数相减或两个负数相减都不会发生溢出signed int si1, si2, result;si1 = 10; si2 = -20;// 如果两个操作数异号,并且结果的符号与第一个操作数不同,则已发生减法溢出// 异或用作一个按位的"不等"操作.要测试符号位置,表达式用INT_MIN进行掩码,这使得只有符号位被设置// 该解决方案只适用于适用补码表示的架构if ((si1 ^ si2) & (((unsigned int)si1 - si2) ^ si1) & INT_MIN) { // 处理错误条件fprintf(stderr, "fail\n");} else {result = si1 - si2;fprintf(stdout, "result = %d\n", result);}// 可移植的先验条件测试if ((si2 > 0 && si1 < INT_MIN + si2) || (si2 < 0 && si1 > INT_MAX + si2)) { // 处理错误条件fprintf(stderr, "fail\n");} else {result = si1 - si2;fprintf(stdout, "result = %d\n", result);	}
}{ // 无符号操作数的减法操作的先验条件测试,以保证不存在无符号回绕现象unsigned int ui1, ui2, udiff;ui1 = 10; ui2 = 20;if (ui1 < ui2) { // 处理错误条件fprintf(stderr, "fail\n");} else {udiff = ui1 - ui2;fprintf(stdout, "udiff = %u\n", udiff);}
}{ // 后验条件测试unsigned int ui1, ui2, udiff;ui1 = 10; ui2 = 20;udiff = ui1 - ui2;if (udiff > ui1) { // 处理错误情况fprintf(stderr, "fail\n");}
}
}

乘法:在C中乘法可以通过使用二元运算符”*”来得到操作数的积。二元运算符”*”的每个操作数都是算术类型。操作数执行普通算术转换。乘法容易产生溢出错误,因为相对较小的操作数相乘时,都可能导致一个指定的整数类型溢出。一般情况下,两个整数的操作数的积总是可以用两个操作数中较大的那个所用的位数的两倍来表示。这意味着,例如,两个8位操作数的积总是可以使用16位类表示,而两个16位操作数的积总是可以使用32位来表示。

void test_integer_security_multiplication()
{
{ // 在无符号乘法的情况下,如果需要高位来表示两个操作数的积,那么结果以及回绕了unsigned int ui1 = 10;unsigned int ui2 = 20;unsigned int product;static_assert(sizeof(unsigned long long) >= 2 * sizeof(unsigned int), "Unable to detect wrapping after multiplication");unsigned long long tmp = (unsigned long long)ui1 * (unsigned long long)ui2;if (tmp > UINT_MAX) { // 处理无符号回绕fprintf(stderr, "fail\n");} else {product = (unsigned int)tmp;fprintf(stdout, "product = %u\n", product);}
}{ // 保证在long long宽度至少是int宽度两倍的系统上,不可能产生符号溢出signed int si1 = 20, si2 = 10;signed int result;static_assert(sizeof(long long) >= 2 * sizeof(int),"Unable to detect overflow after multiplication");long long tmp = (long long)si1 * (long long)si2;if ((tmp > INT_MAX) || (tmp < INT_MIN)) { // 处理有符号溢出fprintf(stderr, "fail\n");} else {result = (int)tmp;fprintf(stdout, "result = %d\n", result);}
}{ // 一般的先验调试测试unsigned int ui1 = 10, ui2 = 20;unsigned int product;if (ui1 > UINT_MAX / ui2) { // 处理无符号回绕fprintf(stderr, "fail\n");} else {product = ui1 * ui2;fprintf(stdout, "product = %u\n", product);}
}{ // 可以防止有符号溢出,而不需要向上强制类型转换到现有位数的两倍的整数类型signed int si1 = 10, si2 = 20;signed int product;if (si1 > 0) { // si1是正数if (si2 > 0) { // si1和si2都是正数if (si1 > (INT_MAX / si2)) { // 处理错误情况fprintf(stderr, "fail\n");}} // end if si1和si2都是正数else { // si1是正数,si2不是正数if (si2 < (INT_MIN / si1)) { // 处理错误情况fprintf(stderr, "fail\n");}} // end if si1是正数,si2不是正数} // end fif si1是正数else { // si1不是正数if (si2 > 0) { // si1不是正数,si2是正数if (si1 < (INT_MIN / si2)) { // 处理错误情况fprintf(stderr, "fail\n");}} // end if si1不是正数,si2是正数else { // si1和si2都不是正数if ((si1 != 0) && (si2 < (INT_MAX / si1))) { // 处理错误情况fprintf(stderr, "fail\n");}} // end if si1和si2都不是正数} // end if si1不是正数product = si1 * si2;fprintf(stdout, "product = %d\n", product);
}
}

使用静态断言static_assert来测试一个常数表达式的值

除法和求余:整数相除时,”/”运算符的结果是代数商的整数部分,任何小数部分都被丢弃,而”%”运算符的结果是余数。这通常称为向零截断(truncation toward zero)。在这两种运算中,如果第二个操作数的值是0,则该行为是未定义的。无符号整数除法不可能产生回绕,因为商总是小于或等于被除数。但并不总是显而易见的是,有符号整数除法也可能导致溢出,因为你可能认为商数始终小于被除数。然而,补码的最小值除以-1时会出现整数溢出。

void test_integer_security_division_remainder()
{// 先验条件:可以通过检查分子是否为整数类型的最小值以及检查分母是否为-1来防止有符号整数除法溢出的发生// 只要确保除数不为0,就可以保证不发生除以零错误signed long sl1 = 100, sl2 = 5;signed long quotient, result;// 此先验条件也可测试余数操作数,以保证不可能有一个除以零错误或(内部)溢出错误if ((sl2 == 0) || ((sl1 == LONG_MIN) && (sl2 == -1))) { // 处理错误情况fprintf(stderr, "fail\n");} else {quotient = sl1 / sl2;result = sl1 % sl2;fprintf(stdout, "quotient = %ld, result = %ld\n", quotient, result);}
}

C11标准规定:如果a/b的商可表示,那么表达式(a/b)*b + a%b应等于a,否则,a/b和a%b的行为都是未定义的。

许多硬件平台上把求余实现为除法运算符的一部分,它可能产生溢出。当被除数等于有符号的整数类型的最小值(负)并且除数等于-1时,求余运算过程中可能会发生溢出。

后验条件:普通的C++异常处理机制并不允许应用程序从一个硬件异常、诸如存取违例或除以零错误一类的故障中恢复。微软确实为处理这类硬件和其它异常情况提供了名为结构化异常处理(Structured Exception Handing, SEH)的设施。结构化异常处理是操作系统提供的一项设施,它不同于C++的异常处理机制。微软为C语言提供了一套扩展,从而使C程序可以处理Win32结构化异常。在Linux环境中,类似于除法错误这样的硬件异常是使用信号机制进行处理的。在Linux环境中,类似于除法错误这样的硬件异常是使用信号机制进行处理的。尤其是,如果除数为0,或者商对于目的寄存器而言值太大,系统将会产生一个浮点异常(Floating Point Exception, SIGFPE)。即使是整数运算,而不是一个浮点运算所产生的异常也引发这种类型的信号。为了防止程序在这种情况下非正常终止,可以利用signal函数调用安装一个信号处理器。

一元反(-):对一个补码表示的有符号的整数求反,也可能产生一个符号错误,因为有符号整数类型的可能值范围是不对称的。

移位:此操作包括左移位和右移位。移位会在操作数上执行整数提升,其中每个操作数都具有整数类型。结果类型是提升后的左操作数类型。移位运算符右边的操作数提供移动的位数。如果该数值为负值或者大于或等于结果类型的位数,那么该行为是未定义的。在几乎所有情况下,试图移动一个负的位数或试图移动比操作数中存在的位数更多的位都表明一个错误(逻辑错误)。这与溢出是不同的,后者是一个表示不足。不要移动一个负的位数或移动比操作数中存在的位数更多的位

void test_integer_security_shift()
{
{ // 消除了无符号整数左移位操作造成的未定义行为的可能性unsigned int ui1 = 1, ui2 = 31;unsigned int uresult;if (ui2 >= sizeof(unsigned int) * CHAR_BIT) { // 处理错误情况fprintf(stderr, "fail\n");} else {uresult = ui1 << ui2;fprintf(stdout, "uresult = %u\n", uresult);}
}{int rc = 0;//int stringify = 0x80000000; // windows/liunx will crash in sprintf functionunsigned int stringify = 0x80000000;char buf[sizeof("256")] = {0};rc = sprintf(buf, "%u", stringify >> 24);if (rc == -1 || rc >= sizeof(buf)) { // 处理错误fprintf(stderr, "fail\n");} else {fprintf(stdout, "value: %s\n", buf); // 128}
}
}

左移:E1<<E2的结果是E1左移E2位的位置,空出的位以0填充。如果E1是有符号类型并且是非负值,且E1 * 2E2能够在结果类型中表示,那么这就是结果值,否则,该行为是未定义的。移位运算符和其它位运算符应仅用于无符号整数操作数。左移位可以用于代替2的幂次数的乘法运算。移位的速度会比乘法快,最好只有当目标是位操作时才使用左移位。

右移:E1>>E2的结果是E1右移E2位的位置。如果E1是一个无符号类型或有符号类型的一个非负的值,则该值的结果是E1/2E2的商的整数部分。如果E1是有符号类型的负值,那么由此产生的值是实现定义的,它可以是算术(有符号)移位。

由于左移位可以取代2的幂次数的乘法,人们通常认为右移位可以取代2的幂次数的除法。然而,只有移位的数值为正时才是如此,原因有两个,首先,对负值右移位是算术还是逻辑移位是实现定义的。其次,即使在已知执行算术右移位的一个平台上,其结果与除法也是不同的。此外,现代编译器可以判断何时使用移位代替除法是安全的,并会在移位在它们的目标架构上更快时做这种替换。出于这些原因,并为了保持代码清晰且易于阅读,只有在我们的目标是位操作时才应该用左移位,在执行传统的算术时,应使用除法。

5.5 整数漏洞:安全缺陷可能是由于硬件层的整数错误或者是跟整数有关的不完善逻辑所造成的。当这些安全缺陷与其它情形结合起来时,就可能会产生漏洞。

回绕:并非所有无符号整数回绕都是安全缺陷。精心定义的无符号整数算术求模属性经常被特意使用,例如,在散列算法和C标准里rand()的示例实现中就都用到了这个属性。

void test_integer_security_wrap_around2()
{
{ // 展示了一个无符号整数回绕导致的实际漏洞的例子size_t len = 1;char* src = "comment";size_t size;size = len - 2;fprintf(stderr, "size = %u, %x, %x, %d\n", size, size, size+1, size+1); // 4294967295, ffffffff, 0, 0char* comment = (char*)malloc(size + 1);//memcpy(comment, src, size); // crashfree(comment);
}{int element_t;int count = 10;// 库函数calloc接受两个参数:存储元素类型所需要的空间和元素的个数.为了求出所需内存的大小,使用元素个数// 乘以该元素类型所需的单位空间来计算.如果计算所得结果无法用类型为size_t的无符号整数表示,那么,尽管分// 配程序看上去能够成功地执行,但实际上它只会分配非常小的内存空间.结果,应用程序对分配的缓冲区的写操作// 可能会越界,从而导致基于堆的缓冲区溢出char* p = (char*)calloc(sizeof(element_t), count);free(p);
}{int off = 1, len = 2;int type_name;// 这里的off和len都声明为signed int.因为根据C标准的定义,sizeof运算符返回的是一个无符号整数类型(size_t),// 整数转换规则要求在那些signed int的宽度等于size_t的宽度的实现上,len - sizeof(type_name)被计算为无符号// 的值,如果len比sizeof运算符返回的值小,那么减法操作会回绕并产生一个巨大的正值std::cout<<"len - sizeof(type_name): "<<len - sizeof(type_name)<<std::endl; // 18446744073709551614if (off > len - sizeof(type_name)) return;// 要消除以上问题,可以把整数范围检查编写为下列替代形式// 程序员仍然必须保证这里的加法操作不会导致回绕,这是通过保证off的值在一个已定义的范围内实现的.为了消除// 潜在的转换错误,在本例中也应当把off和len都声明为size_t类型if ((off + sizeof(type_name)) > len) return;
}
}

转换和截断错误:

void test_integer_security_conversion_truncation()
{
{ // 由转换错误导致的安全漏洞int size = 5;int MAX_ARRAY_SIZE = 10;// 如果size为负数,此检查将通过,而malloc()函数将被传入一个为负的大小.因为malloc()需要size_t类型的参数,// 所以size会被转换成一个巨大的无符号数.当有符号整数类型被转换为一个无符号的整数类型时,会重复加上或减去// 新类型的宽度(2^N),以使结果落在可表示的范围之内.因此,这种转换可能会导致大于MAX_ARRAY_SIZE的值.这种// 错误可以通过把size声明为size_t而不是int来消除if (size < MAX_ARRAY_SIZE) { // 初始化数组char* array = (char*)malloc(size);free(array);} else { // 处理错误fprintf(stderr, "fail\n");}
}{ // 由整数截断错误导致的缓冲区溢出漏洞char* argv[3] = {"", "abc", "123"};unsigned short int total;// 攻击者可能会提供两个总长度无法用unsigned short整数total表示的字符做参数,这样,总长度值将会用比结果// 类型所能表示的最大值大1的数取模截断,函数strlen返回一个无符号整数类型size_t的结果,对于大多数实现而言,// size_t的宽度大于unsigned short的宽度,必然要进行降级操作,strcpy和strcat的执行将导致缓冲区溢出total = strlen(argv[1]) + strlen(argv[2]) + 1;char* buff = (char*)malloc(total);strcpy(buff, argv[1]);strcat(buff, argv[2]);fprintf(stdout, "buff: %s\n", buff);free(buff);
}
}

非异常的整数逻辑错误:

void test_integer_security_integer_logic()
{int* table = nullptr;int pos = 50, value = 10;if (!table) {table = (int*)malloc(sizeof(int) * 100);}// 由于对插入位置pos缺乏必要的范围检查,因此将会导致一个漏洞.因为pos开始时被声明为有符号整数,即传递// 到函数中的值既可正又可负if (pos > 99) return;// 如果pos是一个负值,那么value将会被写入实际缓冲区起始地址pos*sizeof(int)字节之前的位置// 消除安全缺陷:将形式参数pos声明为无符号整数类型,或者把同时检查上届和下界作为范围检查的一部分table[pos] = value; // 等价于: *(int*)((char*)table+(pos*sizeof(int))) = value;free(table);
}

5.6 缓解策略:整数漏洞是由整数类型范围错误(integer type range error)所引起的。例如,发生整数溢出是因为在整数操作时产生了超过特定整数类型表示范围的数值。发生截断错误是因为结果被存放在一个对它而言过小的类型中。数据转换,特别是那些由于赋值或强制类型转换产生的转换,会导致转换后的值超出结果类型范围。

整数类型的选择:应使用无符号整数表示不可能是负数的整数值,而且应使用有符号整数值表示可以为负的值。在一般情况下,应该使用完全可以代表任何特定变量可能值的范围的最小的有符号或无符号类型,以节省内存。当内存消耗不是问题时,你可以决定把变量声明为signed int或unsigned int,以尽量减少潜在的转换错误。

void test_integer_security_type_selection()
{char* argv = "";// 次优的:首先,大小不会是负值,因此,没有必要使用一个有符号整数类型;其次,short整数类型对于可能的对象// 大小可能不具有足够的范围short total1 = strlen(argv) + 1;// 无符号size_t类型,是C标准委员会为了表示对象大小而引入的,此类型的变量都保证有足够的精度来表示一个对象的大小size_t total2 = strlen(argv) + 1;// C11附录K引入一个新类型rsize_t,它被定义为size_t,但明确地用于保存单个对象的大小
#ifdef _MSC_VERrsize_t total3 = strlen(argv) + 1;
#endif
}

任何用于表示一个对象大小的变量,包括用作大小、索引、循环计数器和长度的整数值,如果可以,都应该声明为rsize_t,或声明为size_t。

抽象数据类型:数据抽象可以用标准和扩展的整数类型无法做到的方式支持数据的范围。

任意精度算术:有效地提供了一个新的整数类型,其宽度只受主机系统可用内存限制。有很多任意精度算术的包可供使用,尽管它们主要用于科学计算,然而它们也能用于解决由于表示法缺少精度而引起的整数类型范围错误问题。

GNU多精度算术库(GMP):GNU Multiple-Precision Arithmetic library,是一个用C编写的可移植的库,用于对整数、有理数以及浮点数进行任意精度的算术运算。

C语言解决方案:可以通过在编译器的类型系统中添加任意精度的整数来实现一种防止整数算术溢出的语言解决方案。

范围检查:《C安全编码标准》有一些防止范围错误的规则:

(1).确保无符号整数运算不回绕;

(2).确保整数的转换不会导致数据丢失或错误解释;

(3).确保对有符号整数的操作不会导致溢出。

在不可能发生范围错误的情况下,提供范围检查是不太重要的。

前提条件和后验条件测试:

void test_integer_security_conditions_test()
{
{ // 两个无符号整数加法是否回绕的先验条件测试unsigned int ui1, ui2, usum;ui1 = 10; ui2 = 20;if (UINT_MAX - ui1 < ui2) { // 处理错误情况fprintf(stderr, "fail\n");} else {usum = ui1 + ui2;}
}{ // 确保有符号的乘法运算不会导致溢出的严格的符合性测试signed int si1, si2, result;si1 = 10; si2 = -20;if (si1 > 0) {if (si2 > 0) {if (si1 > (INT_MAX / si2)) { // 处理错误情况fprintf(stderr, "fail\n");}} else {if (si2 < (INT_MIN / si1)) { // 处理错误情况fprintf(stderr, "fail\n");}}} else {if (si2 > 0) {if (si1 < (INT_MAX / si2)) { // 处理错误情况fprintf(stderr, "fail\n");}} else {if ((si1 != 0) && (si2 < (INT_MAX / si1))) { // 处理错误情况fprintf(stderr, "fail\n");}}}result = si1 * si2;
}{ // 后验条件测试可用于检测无符号整数回绕,因为这些操作被定义为取模操作unsigned int ui1, ui2, usum;ui1 = 10; ui2 = 20;usum = ui1 + ui2;// 用这种方式检测范围错误代价可能相对较高if (usum < ui1) { // 处理错误情况fprintf(stderr, "fail\n");}
}
}

安全整数库:可以用来提供安全的整数运算,它们要么成功,要么报告错误。

溢出检测:C标准定义了<fenv.h>头文件来支持浮点异常状态标志、IEC60559和类似的浮点状态信息所需的定向舍入控制模式。

编译器生成的运行时检查:

(1).微软Visual Studio运行时错误检查:可用/RTCc编译标志启用本机运行时检查,它检测导致数据丢失的赋值。在程序的发行(优化)版构建中运行时错误检查不工作。

(2).GCC -ftrapv标志:GCC提供了一个-ftrapv编译器选项,该选项对在运行时检测整数溢出提供了有限的支持。

可验证范围操作:饱和(saturation)和取模回绕(modwrap)算法和限制范围内使用的技术产生的整数结果总是在定义的范围内。这个范围位于整数值MIN和MAX(含)之间,这里MIN和MAX是两个可表示的整数,且MIN比MAX小。

仿佛无限范围整数模型:为了使程序行为与程序员常用的数学推理有更大的一致性,仿佛无限范围(As-If Infinitely Ranged, AIR)整数模型保证,要么整数值相当于使用无限范围的整数得到的,要么就发生运行时异常。

测试和分析:静态分析,无论是由编译器还是一个静态分析仪执行的,都可用于检测源代码中潜在的整数范围错误。这些问题一旦被确定,就可以通过使用适当的整数类型或添加逻辑修改你的程序,以确保可能值的范围在你所使用的类型范围内,从而修正它们。静态分析容易产生误报(false positive)。误报是被编译器或分析仪错误地诊断为错误的编程结构。提供既可靠(无漏报)又完备(无误报)的分析是很难(或不可能)的。免费提供的开源静态分析工具的两个例子是ROSE和Splint。

以上代码段的完整code见:GitHub/Messy_Test

GitHub:https://github.com/fengbingchun/Messy_Test

相关文章:

8.3折特惠票仅剩3天!「2019 嵌入式智能国际大会」全日程大公开!

8.3折特惠票仅剩3天立即抢购&#xff1a;https://t.csdnimg.cn/otBk还有5天&#xff0c;大伙期待的「2019嵌入式智能国际大会」正式开幕了&#xff01;2019年12月6日-7日&#xff0c;我们在深圳市人才研修院见&#xff01;大会以“万物互联泛在智能”为主题&#xff0c;邀请30位…

iOS点击空白收回键盘

//点击空白收回键盘 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; }

第一个net-mvc程序

结构 视图层 <% Page Language"C#" Inherits"System.Web.Mvc.ViewPage<dynamic>" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">…

iOS 储存用户信息设置封装 直接调用即可(部分是代码片段)

、、、、、、、、、、、、、、、、、、首先 定义 UserInfo #import <Foundation/Foundation.h> interface UserInfo : NSObject //用户id property (nonatomic, strong) NSString *userID; //用户名 property (nonatomic, strong) NSString *userName; //密码 propert…

ASN.1简介及OpenSSL中ASN.1接口使用举例

ASN.1(Abstract Syntax Notation One)是一套标准&#xff0c;是描述数据的表示、编码传输、解码的灵活的记法。它提供了一套正式、无歧义和精确的规则以描述独立于特定计算机硬件的对象结构。OpenSSL的编码方法就是基于该标准。ASN.1是一种结构化的数字对象描述语言&#xff0c…

谁是当今最顶级的技术?SQL、Java、Python、C++ 皆上榜!

【12月公开课预告】&#xff0c;入群直接获取报名地址12月11日晚8点直播主题&#xff1a;人工智能消化道病理辅助诊断平台——从方法到落地12月12日晚8点直播&#xff1a;利用容器技术打造AI公司技术中台12月17日晚8点直播主题&#xff1a;可重构计算&#xff1a;能效比、通用性…

将表里的数据批量生成INSERT语句的存储过程 增强版

原文:将表里的数据批量生成INSERT语句的存储过程 增强版将表里的数据批量生成INSERT语句的存储过程 增强版 有时候&#xff0c;我们需要将某个表里的数据全部或者根据查询条件导出来&#xff0c;迁移到另一个相同结构的库中 目前SQL Server里面是没有相关的工具根据查询条件来生…

通过OpenSSL的接口实现Base64编解码

对openssl genrsa产生的rsa私钥pem文件&#xff0c;使用普通的base64解码会有问题&#xff0c;如使用https://blog.csdn.net/fengbingchun/article/details/85218653 中介绍的方法&#xff0c;一是有可能不能从返回的结果中直接使用strlen来获得最终字符的大小&#xff0c;因为…

激辩:机器究竟能否理解常识?

【12月公开课预告】&#xff0c;入群直接获取报名地址12月11日晚8点直播主题&#xff1a;人工智能消化道病理辅助诊断平台——从方法到落地12月12日晚8点直播&#xff1a;利用容器技术打造AI公司技术中台12月17日晚8点直播主题&#xff1a;可重构计算&#xff1a;能效比、通用性…

Mac OS X 下Node.js开发环境的搭建

1.安装Xcode2.安装Homebrew 谷歌搜索Homebrew 复制命令行 打开终端 粘贴命令行 点击回车 安装 输入密码等2.安装Nodejs利用Homebrew安装nodejs打开终端 输入 &#xff1a;brew install nodejs 回车查询nodejs版本&#xff1a;node --version3.安装文档数据库 MongoDB打开终…

.NET 使用 MySql.Data.dll 动态库操作MySql的帮助类--MySqlHelper

.NET 使用 MySql.Data.dll 动态库操作MySql的帮助类--MySqlHelper 參考演示样例代码&#xff0c;例如以下所看到的&#xff1a; /// <summary>/// MySql 数据库操作类/// </summary>public class MySqlHelper{/// <summary>/// MysqlConnection/// </summ…

Instagram个性化推荐工程中三个关键技术是什么?

作者 | Ivan Medvedev&#xff0c;Haotian Wu&#xff0c;Taylor Gordon译者 | 陆离编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 【导语】近期&#xff0c;Facebook 在博客上分享了第一篇详细介绍 Explore 系统关键技术&#xff0c;以及 I…

iOS UIbutton 点击无反应的几种情况

1、UIButton不能点击情况的第一种是&#xff0c;你将button添加到一个不能响应点击事件的View里。如你将button添加到UIImageView中&#xff0c;解决办法只需将UIImageView的 userInteractionEnabled设为YES即可。 例如&#xff1a; self.headImgV [[UIImageView alloc] ini…

C和C++安全编码笔记:格式化输出

C标准中定义了一些可以接受可变数量参数的格式化输出参数&#xff0c;参数中包括一个格式字符串。printf()和sprintf()都是格式化输出函数的例子。格式化输出函数是由一个格式字符串和可变数目的参数构成的。在效果上&#xff0c;格式化字符串提供了一组可以由格式化输出函数解…

谈谈UI架构设计的演化

谈谈UI架构设计的演化 经典MVC 在1979年&#xff0c;经典MVC模式被提出。 在当时&#xff0c;人们一直试图将纯粹描述思维中的对象与跟计算机环境打交道的代码隔离开来&#xff0c;而Trygve Reenskaug在跟一些人的讨论中&#xff0c;逐渐剥离出一系列的概念&#xff0c;最初是T…

JWT(JSON Web Token)简介及实现

JWT(JSON Web Token)&#xff1a;是一个开放标准(RFC 7519)&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于在各方之间作为Json对象安全地传输信息。由于此信息是经过数字签名的&#xff0c;因此可以被验证和信任。可以使用HMAC SHA256或RSA等对JWT进行签名。 JW…

iOS UIImageView 加载含有汉字的url处理方法

NSString *url [model.pic stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; [self.headImgView sd_setImageWithURL:[NSURL URLWithString:url]];

《评人工智能如何走向新阶段》后记

由AI科技大本营下载自视觉中国自《评人工智能如何走向新阶段》一文发表&#xff08;在内部&#xff09;后&#xff0c;引来了中外专家、草根们的广泛议论&#xff0c;有深有浅&#xff0c;其中似有一些思考价值&#xff0c;故录入本文后记&#xff1a; 中外专家、草根们23条议…

用XCA(X Certificate and key management)可视化程序管理SSL 证书(3)--创建自己定义的凭证管理中心(Certificate Authority)...

在第“用XCA&#xff08;X Certificate and key management&#xff09;可视化程序管理SSL 证书&#xff08;2&#xff09;---创建证书请求”章节中&#xff0c;我们介绍了怎样用XCA创建SSL证书请求&#xff08;Certificate Request&#xff09;&#xff0c;在一章节中&#xf…

C和C++安全编码笔记:并发

并发是一种系统属性&#xff0c;它是指系统中几个计算同时执行&#xff0c;并可能彼此交互。一个并发程序通常使用顺序线程和(或)进程的一些组合来执行计算&#xff0c;其中每个线程和进程执行可以在逻辑上并行执行的计算。这些进程和(或)线程可以在单处理器系统上使用分时抢占…

《评人工智能如何走向新阶段》后记(再续1)

由AI科技大本营下载自视觉中国中外专家、草根对《评人工智能如何走向新阶段》一文进行广泛议论&#xff0c;已在《后记》中发表原创&#xff08;未加修改&#xff09;的23条议论&#xff0c;现再续发24-30条如下&#xff1a; 24.最近半年来&#xff0c;人工智能的发展重心逐渐…

iOS UITextView 随键盘弹出界面上移

- (void)textViewDidBeginEditing:(UITextView *)textView { CGRect frame textView.frame; int offSet frame.origin.y 70 - (self.view.frame.size.height - 216.0); //iphone键盘高度为216.iped键盘高度为352 [UIView beginAnimations:"ResizeForKeyboard" co…

H3C 交换机命名规则

例&#xff1a;H3C-S5500-28C-EIH3C&#xff1a;为固定值&#xff0c;就是“H3C”这个品牌S的位置&#xff1a;代表产品系列『S 代表交换机SR 代表业务路由器』第一个5的位置:代表产品子系列号『3系为千兆上行,百兆下行的合适交换机 例:S3600 S31005系为全千兆的盒式交换机 …

iOS 时间选择器封装(含三种模式)

#import <UIKit/UIKit.h> typedef enum : NSUInteger { DatePickerViewDateTimeMode,//年月日,时分 DatePickerViewDateMode,//年月日 DatePickerViewTimeMode//时分 } DatePickerViewMode; protocol DateTimePickerViewDelegate <NSObject> optional /** * 确定按…

C和C++安全编码笔记:总结

《C和C安全编码》(原书第2版)这本书是2013年出版的。 这里是基于之前所有笔记的简单总结&#xff0c;笔记列表如下&#xff1a; 字符串&#xff1a;https://blog.csdn.net/fengbingchun/article/details/105325508 指针诡计&#xff1a;https://blog.csdn.net/fengbingchun/…

《评人工智能如何走向新阶段》后记(再续2)

由AI科技大本营下载自视觉中国从朋友那里获知&#xff0c;有一块供大家自由议论人工智能的园地&#xff08;内部的&#xff09;&#xff0c;我通过有关关系进入后&#xff0c;一览之余&#xff0c;果然生动活泼&#xff0c;没有学究气&#xff0c;从已发表的30条议论来看。有原…

Dokku和Docker的完美配合

看到一篇不错的文章&#xff0c;收藏一下&#xff1a; 【编者的话】本文作者介绍了如何在单机上将Dokku和Docker结合。Dokku是一个小型的PaaS平台&#xff0c;只需使用Git将代码push到对应的仓库上就能自动触发部署&#xff0c;构建过程非常简单。但是Dokku对于用户来说&#x…

iOS封装分页效果

#import <UIKit/UIKit.h> interface WPageTitleView : UIView property (nonatomic,assign) NSInteger selectedIndex; //添加参数数组 property (nonatomic,strong) NSArray *titles; property (nonatomic,copy) void (^buttonSelected)(NSInteger index); end #impo…

Windows/Linux TCP Socket网络编程简介及测试代码

典型的网络应用是由一对程序(即客户程序和服务器程序)组成的&#xff0c;它们位于两个不同的端系统中。当运行这两个程序时&#xff0c;创建了一个客户进程和一个服务器进程&#xff0c;同时它们通过从套接字(socket)读出和写入数据在彼此之间进行通信。开发者创建一个网络应用…

《评人工智能如何走向新阶段》后记(再续3)

由AI科技大本营下载自视觉中国35.阿里巴巴旗下芯片公司平头哥在乌镇互联网大会上宣布开源低功耗微控制芯片&#xff08;MCU&#xff09;设计平台&#xff0c;这一平台面向 AIoT 时代的定制化芯片设计需求&#xff0c;目标群体包括芯片设计公司、IP 供应商、高校及科研院所等&am…