1、常见地址解释
- 0xABADCAFE:初始化所有空闲内存以捕获错误指针的启动值
- 0xBAADF00D:由Microsoft的LocalAlloc(LMEM_FIXED)用于标记未初始化的已分配堆内存
- 0xBADCAB1E:当断开与调试器的连接时返回给Microsoft eVC调试器的错误码
- 0xBEEFCACE:被Microsoft .NET用作资源文件中的magic number
- 0xCCCCCCCC:由Microsoft的c++调试运行库用于标记未初始化的堆栈内存
由Microsoft的c++调试运行库用于标记未初始化的堆内存 - 0xDEADDEAD:当用户手动启动崩溃时使用的Microsoft Windows STOP错误代码
- 0xFDFDFDFD:用于微软的c++调试堆标记“无人之地”保护字节在分配堆内存之前和之后
- 0xFEEEFEEE:由微软的HeapFree()用来标记释放的堆内存
2、利用指针地址偏移填结构体值
void *data = malloc(sizeof(int) * 4); *(int*)(data + sizeof(int) * 0) = 1; *(int*)(data + sizeof(int) * 1) = 2; *(int*)(data + sizeof(int) * 2) = 3; *(int*)(data + sizeof(int) * 3) = 4; printf("%dt%dn", *(int*)data, *(int*)(data + sizeof(int) * 3));
这里的int可以转化成任意的struct,这样做的好处是不需要在代码中额外定义struct来赋值,只需要一个地址,一般是使用内存池开辟一块空间。
3、矩阵相乘
随机产生一个mn的100以内的正整数矩阵,输出这个矩阵,再随机产生一个no的100以内的正整数矩阵,输出这个矩阵,求这两个矩阵的乘积,放在一个新的矩阵中,输出所得矩阵。(m、n、o由键盘输入,需要判断m、n、o的合法性)
#include#include #define MATRIX_MAXSIXE 10 int main() { //定义三个矩阵 int a[MATRIX_MAXSIXE][MATRIX_MAXSIXE],b[MATRIX_MAXSIXE][MATRIX_MAXSIXE],c[MATRIX_MAXSIXE][MATRIX_MAXSIXE]; //用于输入两个矩阵的行和列 int m,n,p,q; //用于for循环 int i,j,k; //输入第一个矩阵的行和列 printf("Enter rows and columns of first matrix:"); scanf("%d%d",&m,&n); 输入第二个矩阵的行和列 printf("Enter rows and columns of second matrix:"); scanf("%d%d",&p,&q); //矩阵容错 if(m==0 || n==0 || p==0 || q==0 || m>MATRIX_MAXSIXE || n>MATRIX_MAXSIXE || p>MATRIX_MAXSIXE || q>MATRIX_MAXSIXE) { printf("nSorry!!!! Matrix multiplication can't be done"); return 0; } //求乘的两个矩阵满足条件:第一个矩阵的列为第二个矩阵的行 if(n==p) { printf("nfirst matrix:n"); for(i=0;i 4、C语言位域
位域的定义和使用
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行 *** 作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:
struct bs { int a:8; // 这里8后可以跟 , 或 ; 都是合法的。 int b:2; int c:6; };5、运行时修改代码
#pragma comment(linker,"/SECTION:.text,RW") #include#ifdef _DEBUG #define OFFSET 0x0C #else #define OFFSET 0x01 #endif int *p; int p2() { int a; a=2; return a; } int main() { p=(int *)((char *)p2+OFFSET); printf("p2==0x%08x,p==0x%08x,*p==%dn",(char *)p2,p,*p); *p=3; printf("p2()==%dn",p2()); return 0; } //p2==0x00401000,p==0x0040100c,*p==2 //p2()==3 // 6、确定被调用的宿主问题
#includevoid whocallme(); void fun1() { whocallme(); } void fun2() { whocallme(); } void fun3() { fun1(); } void whocallme() { int *_esp,i; __asm { mov eax,esp mov _esp,eax } printf("nfun1,fun2,fun3,_esp=%08x,%08x,%08x,%08xn",(int)fun1,(int)fun2,(int)fun3,(int)_esp); for (i=0;i<100;i++) if (_esp[i]==(int)_esp) break; if (i<100) { printf("ret addr=%08xn",_esp[i+2]); if ((int)fun1<=_esp[i+2] && _esp[i+2]<(int)fun2) printf("fun1 callmen"); if ((int)fun2<=_esp[i+2] && _esp[i+2]<(int)fun3) printf("fun2 callmen"); } } void main() { fun2(); fun1(); } //C:tmptmpDebug>tmp // //fun1,fun2,fun3,_esp=00401000,00401020,00401040,0012fe84 //ret addr=0040102e //fun2 callme // //fun1,fun2,fun3,_esp=00401000,00401020,00401040,0012fe84 //ret addr=0040100e //fun1 callme // //C:tmptmpDebug>cd ..release // //C:tmptmpRelease>tmp // //fun1,fun2,fun3,_esp=00401000,0040100a,00401014,0012ff5c //ret addr=00401012 //fun2 callme // //fun1,fun2,fun3,_esp=00401000,0040100a,00401014,0012ff5c //ret addr=00401008 //fun1 callme // 7、while
尽量不要使用while (条件)更不要使用
while (组合条件)要使用
while (1) { if (条件1) break; //... if (条件2) continue; //... if (条件3) return; //... }因为前两种写法在语言表达意思的层面上有二义性,只有第三种才忠实反映了程序流的实际情况。
典型如:
下面两段的语义都是当文件未结束时读字符while (!feof(f)) { a=fgetc(f); //... b=fgetc(f);//可能此时已经feof了! //... }而这样写就没有问题:
while (1) { a=fgetc(f); if (feof(f)) break; //... b=fgetc(f); if (feof(f)) break; //... }类似的例子还可以举很多。
8、结构体初始化
Linux Kernel的例子static struct usb_driver usb_storage_driver = { .owner = THIS_MODULE, .name = "usb-storage", .probe = storage_probe, .disconnect = storage_disconnect, .id_table = storage_usb_ids, };9、c鲜为人知的运算符<----
#includeint main() { int x = 10; while (0<----x) { printf("%d",x); } return 0; } 8 6 4 2#includeint main(int argc, char** argv) { int x = 10; while (x --> 0) { printf("%d ", x); } return 0; } 9 8 7 6 5 4 3 2 1 010、glibc中高度优化的strlen
正常的strlen
size_t strlen(const char *p) { const char *s = p; while (*p) p++; return (size_t)(p - s); }glibc中高度优化的strlen。可以一次性计算 8 个字节内有没有包含 0,写性能极致优化的代码,要考虑这些局部优化的手段。
size_t strlen (const char *str) { const char *char_ptr; const unsigned long int *longword_ptr; unsigned long int longword, himagic, lomagic; for (char_ptr = str; ((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0; ++char_ptr) if (*char_ptr == '') return char_ptr - str; longword_ptr = (unsigned long int *) char_ptr; himagic = 0x80808080L; lomagic = 0x01010101L; if (sizeof (longword) > 4) { himagic = ((himagic << 16) << 16) | himagic; lomagic = ((lomagic << 16) << 16) | lomagic; } for (;;) { longword = *longword_ptr++; if (((longword - lomagic) & ~longword & himagic) != 0) { const char *cp = (const char *) (longword_ptr - 1); if (cp[0] == 0) return cp - str; if (cp[1] == 0) return cp - str + 1; if (cp[2] == 0) return cp - str + 2; if (cp[3] == 0) return cp - str + 3; if (sizeof (longword) > 4) { if (cp[4] == 0) return cp - str + 4; if (cp[5] == 0) return cp - str + 5; if (cp[6] == 0) return cp - str + 6; if (cp[7] == 0) return cp - str + 7; } } } }11、快速范围判断
if (j >= min && j <= max)存在的两次分支判断,可以减少为一次,写成:
if ((int32_t)((j - min) | (max - j)) >= 0)进一步多个变量范围判断还可以继续优化成,注意下面都是无符号整数:
if ( ( (x - minx) | (maxx - x) | (y - miny) | (maxy - y) ) >= 0)让四次判断减少为 1 次判断,这背后的道理很简单,当范围是变量时,多算两次减法完全不影响,但是多一两次判断,对性能的影响是很大的 https://godbolt.org/z/eN_GK-
链接:https://zhuanlan.zhihu.com/p/147039093
12、整数快速除以255
整数快速除以 255 这个事情非常常见,例如图像绘制/合成,音频处理,混音计算等。网上很多比特技巧,
#define div_255_fast(x) (((x) + (((x) + 257) >> 8)) >> 8)当 x 属于 [0, 65536] 范围内,该方法的误差为 0。过去不少人简略的直接用 >> 8 来代替,然而这样做会有误差,连续用 >>8 代替 / 255 十次,误差就累计到 10 了。上面的宏可以方便的处理 8-16 位整数的 /255 计算,经过测试 65536000 次计算中,使用 /255的时间是 325ms,使用div_255_fast的时间是70ms,使用 >>8 的时间是 62ms,div_255_fast 的时间代价几乎可以忽略。进一步可以用 SIMD 写成:// (x + ((x + 257) >> 8)) >> 8
static inline __m128i _mm_fast_div_255_epu16(__m128i x) { return _mm_srli_epi16(_mm_adds_epu16(x, _mm_srli_epi16(_mm_adds_epu16(x, _mm_set1_epi16(0x0101)), 8)), 8); }这样可以同时对 8 对 16 bit 的整数进行 / 255 运算,照葫芦画瓢,还可以改出一个 / 65535 ,或者 / 32767 的版本来。对于任意大于零的整数,他人总结过定点数的方法,x86 跑着一般,x64 下还行:
static inline uint32_t fast_div_255_any (uint32_t n) { uint64_t M = (((uint64_t)1) << 32) / 255; // 用 32.32 的定点数表示 1/255 return (M * n) >> 32; // 定点数乘法:n * (1/255) }这个在所有整数范围内都有效,但是精度有些不够,所以要把 32.32 的精度换成 24.40 的精度,并做一些四舍五入和补位:static
inline uint32_t fast_div_255_accurate (uint32_t n) { uint64_t M = (((uint64_t)1) << 40) / 255 + 1; // 用 24.40 的定点数表示 1/255 return (M * n) >> 40; // 定点数乘法:n * (1/255) }该方法能够覆盖所有 32 位的整数且没有误差,有些编译器对于常数整除,已经可以生成类似 fast_div_255_accurate 的代码了,整数除法是现代计算机最慢的一项工作,动不动就要消耗 30 个周期,常数低的除法除了二次幂的底可以直接移位外,编译器一般会用定点数乘法模拟除法。编译器生成的常数整除代码主要是使用了 64 位整数运算,以及乘法,略显复杂,对普通 32 位程序并不是十分友好。因此如果整数范围属于 [0, 65536] 第一个版本代价最低。
13、浮点数格式C代码
#include#include #include #include int main() { float f; double d; char bs[65]; char b[65]; char s[80]; unsigned char *p; char e[12]; char *t; int ex; while (1) { printf("Input a float point number:");fflush(stdout); rewind(stdin); fgets(s,80,stdin); if (1==sscanf(s,"%f",&f) && 1==sscanf(s,"%lf",&d)) break; } printf("f=%gn",f); p=(unsigned char *)&f; printf("hex=%02X %02X %02X %02Xn",p[3],p[2],p[1],p[0]); ltoa(*(long *)&f,b,2); sprintf(bs,"%032s",b); printf("bin=%sn",bs); printf("bin=%.1s %.8s %sn",bs,bs+1,bs+9); strncpy(e,bs+1,8);e[8]=0; ex=strtol(e,&t,2); printf(" %c %-4d-127 1.%sn",(bs[0]=='0')?'+':'-',ex,bs+9); ex-=127; printf(" %c %-8d 1.%sn",(bs[0]=='0')?'+':'-',ex,bs+9); printf("nd=%lgn",d); p=(unsigned char *)&d; printf("hex=%02X %02X %02X %02X %02X %02X %02X %02Xn",p[7],p[6],p[5],p[4],p[3],p[2],p[1],p[0]); _i64toa(*(__int64 *)&d,b,2); sprintf(bs,"%064s",b); printf("bin=%sn",bs); printf("bin=%.1s %.11s %sn",bs,bs+1,bs+12); strncpy(e,bs+1,11);e[11]=0; ex=strtol(e,&t,2); printf(" %c %-6d-1023 1.%sn",(bs[0]=='0')?'+':'-',ex,bs+12); ex-=1023; printf(" %c %-11d 1.%sn",(bs[0]=='0')?'+':'-',ex,bs+12); return 0; } //Input a float point number:0.125 //f=0.125 //hex=3E 00 00 00 //bin=00111110000000000000000000000000 //bin=0 01111100 00000000000000000000000 // + 124 -127 1.00000000000000000000000 // + -3 1.00000000000000000000000 // //d=0.125 //hex=3F C0 00 00 00 00 00 00 //bin=0011111111000000000000000000000000000000000000000000000000000000 //bin=0 01111111100 0000000000000000000000000000000000000000000000000000 // + 1020 -1023 1.0000000000000000000000000000000000000000000000000000 // + -3 1.0000000000000000000000000000000000000000000000000000 // 14、谁来跟我挑战效率?从文件d.txt中逐个读出能读的浮点数,比如"1.0,3.5,2.2 …"
引用自
#includeint n,r; double d; FILE *f; void main() { f=fopen("d.txt","r"); n=0; while (1) { r=fscanf(f,"%lf",&d); if (1==r) { n++; printf("[%d]==%lgn",n,d);//可以试试注释掉这句以后的速度 } else if (0==r) { fscanf(f,"%*c"); } else break; } fclose(f); } 15、受 SQLite 多年青睐,C 语言到底好在哪儿?
SQLite 近日发表了一篇博文,解释了为什么多年来 SQLite 一直坚持用 C 语言来实现,以下是正文内容:
C 语言是最佳选择
从2000年5月29日发布至今,SQLite 一直都是用 C 语言实现。C 一直是实现像 SQLite 这类软件库的最佳语言。目前,还没有任何计划要采用另外一门语言对 SQLite 进行重新开发。为什么 C 语言是实现 SQLite 的最佳选择?原因主要体现在这几个方面:
性能
兼容性
低依赖性
稳定性1、性能
像 SQLite 这类库要求速度必须要快。SQLite 的速度就很快,它比文件系统快 35%(详情可以参考这两个示例:Internal Versus External BLOBs 和 35% Faster Than The Filesystem)。
而 C 语言就能实现快速编写代码。C 语言通常被描述为“可移植性的汇编语言”。它使开发人员能够尽可能靠近底层硬件进行编码,同时仍然可以跨平台保持可移植性。
平常,我们可能会看到有人描述某种语言“像 C 语言一样快”,却不会看到有人说,作为通用目的编程时,会有一门语言“比 C 语言快”,因为这种语言真的不存在。
2、兼容性
几乎所有系统都能调用 C 语言编写的库,但其他语言就不尽然。例如,用 Java 编写的 Android 应用能够调用 SQLite(通过适配器)。 如果用 Java 编写 SQLite,那么对 Android 来说可能会更方便,因为这会使接口更简单。但在 iPhone 上,应用程序是用 Objective-C 或 Swift 编写的,它们都不能调用用 Java 编写的库。 因此,如果用 Java 编写,SQLite 将无法在 iPhone 上使用。
3、低依赖性
用 C 语言编写的库对运行时没有很强的依赖。SQLite 的最低配置也只要求 C 库中的这些方法:
memcmp() memcpy() memmove() memset() strcmp() strlen() strncmp()在更完整的构建中,SQLite 也使用诸如 malloc() 和 free() 之类的库例程以及用于打开,读取,写入和关闭文件的 *** 作系统接口。 但即便如此,依赖的数量也很少。
4、稳定性
C 语言易于理解,契合了 SQLite 的要求,适合 SQLite 的开发。
为什么 SQLite 不使用面向对象的语言?
开发人员可能无法想象用“非面向对象”来开发一个像 SQLite 这样复杂的系统会是什么样子。所以 SQLite 为什么不使用 C++ 或者 Java 来开发呢?1、用 C++ 或 Java 编写的库通常只能由以相同语言编写的应用程序使用。 使用 Haskell 或 Java 编写的应用程序很难调用用 C++ 编写的库。 另一方面,用 C 语言编写的库可以从任何编程语言调用。
2、面向对象是设计模式,而不是编程语言。 你可以使用任何所需语言(包括汇编语言)进行面向对象编程。 某些语言(例如:C++ 或 Java)可以使面向对象更容易,但你仍然可以用像 C 这样的语言进行面向对象的编程。
3、面向对象不是唯一有效的设计模式。对象通常是分解问题的好方法。 但不是唯一的方法,也不总是分解问题的最佳方法。 有时好的旧程序代码更容易编写,更易于维护和理解,并且比面向对象的代码更快。
4、SQLite 进行开发时,Java 还不是一门成熟的语言,C++ 会成熟一点,但当时要找到两种能以 相同方式工作的 C++ 编译器比较困难。相比之下,C 语言是个不错的选择。虽然,这种情况现在有所改善,但为此对 SQLite 重新开发并没有什么好处。
为什么 SQLite 不使用"安全"语言编写?
使用“安全”语言不易发生内存泄露、数组溢出等的安全问题。最近,许多人好像对 Rust 和 Go 这样的“安全”语言感兴趣。但 SQLite 为什么不使用呢?1、SQLite 出现后的 10 年时间里,所谓的“安全”语言还不存在。虽然 SQLite 可以用 Rust 或者 Go 重新编写,但这样可能会引入更多难以修复的 Bug,进而会影响编码速度。
2、“安全”编程语言解决简单的问题:像内存泄露、数组溢出等。在解决 SQL 计算结果这类的问题上,并不如 C 语言好用。
3、“安全”语言可防止安全漏洞,但 SQLite 并非一个对安全敏感的库。如果应用运行了不受信任的 SQL,那它可能已经存在更大的安全问题,而这是“安全”语言无法修复的问题。
4、一些“安全”语言(如 Go 语言)不喜欢使用 assert(),但这是保持 SQLite 可维护性的重要前提。
5、“安全”语言会插入额外的机器分支来执行其他 *** 作。但在正确的代码中,这些分支并不会被采用。所以机器代码不能 100% 被测试到,可这恰恰是 SQLite 质量检测的重要组成部分。
6、“安全”语言会在内存不足(OOM)时请求终止,而 SQLite 的设计是遇到 OOM 时能重新恢复。目前,还不知道如何利用“安全”语言实现这一点。
7、现有的“安全”语言都比较新,SQLite 开发员对它们的出现表示赞赏,但依然认为 C 语言更适合目前的开发工作。
文章最后表示,SQLite 可能会考虑使用 Rust 重新开发,但不太可能使用 Go 语言,因为它对 assert() 不友好。但其实 Rust 目前的条件并不足以对 SQLite 进行重新开发,它还需要继续发展,详情请查看原文。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)