当前位置: 首页 > news >正文

郑州企业网站建设费用网站权重怎么做的

郑州企业网站建设费用,网站权重怎么做的,电子专业简历模板,怎么做网站流量竞品分析目录标题 前言有关函数的几个性质介绍可变参数的用法介绍可变参数的一个注意事项可变参数的底层原理va_listva_endva_startva_arg_INTSIZEOF 可变参数的注意事项日志的实现日志的测试 前言 在上一篇文章中我们介绍了TCP协议有关的函数#xff0c;大致就是服务端先通过listen函… 目录标题 前言有关函数的几个性质介绍可变参数的用法介绍可变参数的一个注意事项可变参数的底层原理va_listva_endva_startva_arg_INTSIZEOF 可变参数的注意事项日志的实现日志的测试 前言 在上一篇文章中我们介绍了TCP协议有关的函数大致就是服务端先通过listen函数将自己的套接字设置为监听状态然后客户端通过connect函数向对应的服务端发起链接请求最后服务端使用accept函数接收客户端发送过来的链接请求并通过返回值来进行通信那么这就是TCP协议通信的大致过程因为上面的每一个函数调用都有可能会出现问题所以在出现问题的时候我们都是直接朝屏幕上进行打印比如说create socket errorbind error等等等但是问题也是分等级的啊如果这样无脑的朝屏幕上进行打印的话肯定会影响到程序的使用体验所以我们可以对问题进行分类并且将不同程度的问题输出到不同的文件当中这样未来想要查找问题的时候就只需要打开指定的文件即可那么我们将程序问题分为以下几种 #define DEBUG 0 #define NORMAL 1 #define WARNING 2 #define ERROR 3 #define FATAL 4数字越大表示出现的问题越严重DEBUG( 0 )和NOEMAL( 1 )基本上就表示没什么问题WARNING3就表示稍微有点问题但是不影响程序的正常运行ERROR就表示当前的比较严重会影响程序的正常运行FATAL就表示当前的问题是非常致命的会造成严重的后果有了分类还不够因为一个系统下会存在多个进程每个进程都可能会发送问题那我们怎么知道当前这个问题是属于哪个时间段哪个进程发送的呢所以在发送报错消息的时候我们往往是会添加一些系统信息的比如说时间搓和进程pid等等有了这些信息之后我们就可以知道当前问题是谁发送的该问题属于哪一个等级最后就可以添加问题的内容所以我们这里可以创建一个函数来专门完成这个错误发送的功能该函数的声明如下 void logMessage(int level, const char *format)发送数据的格式就是下面这样 [日志等级] [时间戳/时间] [pid] [messge] [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]但是有时候问题的内容是需要添加一些变量的值就好比printf函数一样那么这个时候就存在一个问题我们怎么知道使用者会传递什么类型的参数呢我们怎么知道参数的个数是多少呢所以这里就得在函数当中添加可变参数列表也就是在参数声明的末尾添加三个点 void logMessage(int level, const char *format, ...)这样我们就可以传递任意类型任意数量的参数就好比下面这样 logMessage(WARNING,创建socket失败 sokcet:%d,%f,%c,-1,3.14,c);那么这就是我们要实现的logMessage函数的功能在一个程序中调用该函数就可以向指定的位置打印该程序在运行过程中的信息我们把这里的信息所组成的集合称之为日志。因为大多数同学在次之前并没有了解可变参数的原理和用法所以在实现logMessage函数之前我们先来介绍一下可变参数。 有关函数的几个性质介绍 第一个性质如果函数没有形式参数也可以给函数传递参数比如说下面的代码 void print() {printf(当前函数不需要传递参数); } int main() {int a 10;int b 20;int c 30;print(a,b,c);return 0; }程序在运行的时候是不会出现问题的 第二个性质在c语言中只要发生了函数调用并且传递了参数就必定会形成临时变量这里依然用上面的代码来举例子对其进行反汇编就可以看到下面这样的内容 可以看到在使用指令call调用print函数之前先用move指令进行了赋值操作因为不同版本的编译器看到的效果不太一样这里没有看到push指令的压栈操作但是不妨碍传递参数就必定形成临时变量的结论其次大家不难发现这里在传递参数的时候是以c b a的顺序来进行传递但是在函数调用中参数传递的顺序却是a,b,c所以这里就可以得到函数调用的第三个性质临时拷贝是以从右往左的顺序形成的那么这就是函数调用有关的三个性质。 可变参数的用法介绍 这里通过一段代码来带着大家理解可变参数的使用方法 #includestdio.h #includeWindows.h //num表示可变参数的个数 int FindMax(int num, ...) {va_list arg;va_start(arg, num);//创建变量记录当前最大值int max va_arg(arg, int);for (int i 0; i num - 1; i){int cur va_arg(arg, int);if (max cur){max cur;}}va_end(arg);return max; } int main() {int max FindMax(5, 101, 32, 324, 178, 78);printf(最大的值为%d, max);return 0; }首先该函数实现的功能就是在一定数量的可变参数中找到最大值程序的运行结果如下 对于这段代码大家可能就对这几点感到困惑va_listva_startva_argva_end首先这几个东西都是与 可变参数有关的宏va_list本质上就是一个char类型的指针,用它创建变量arg指向可变参数中的变量 typedef char* va_list;因为当前函数的参数分为固定参数和可变参数而传递参数又是从右向左进行传递的函数的栈帧又是从下往上进行生长的所以我们当前需要一个功能让arg指向可变参数部分那么宏va_start就是负责实现该功能 但是va_start也不知道可变参数的起始位置在哪所以我们得把紧挨着可变参数的固定参数传递给va_start因为参数的传递是连续的所以他就可以根据该参数的地址和该参数的类型找到第一个可变参数的地址然后就可以随水推舟再根据可变参数的类型和地址找到其他可变参数那么这就是va_start的功能(初始化va_list定义的变量)以及为什么上面的代码将固定参数num传递给va_start的原因这里大家可能会有疑问如果图片是下面这样该如何传递紧挨着的固定参数呢 我们可以在尝试一下然后就可以发现这里是直接报错的可变参数列表的右边是不能添加固定参数的 找到了可变参数我们就可以读取可变参数的内容以及向下找到其他的可变参数那么这里就可以用到宏va_arg他就可以返回当前arg指针指向的内容以及帮助arg指针找到下一个可变参数使用这个宏的时候得传递当前指针指向的数据类型因为我们传递的都是int类型参数所以第二个参数就填int最后一个arg_end就是用来将指针变量arg置空那么这就是与可变参数有关的4个宏的作用通过上面的介绍大家应该能够知道可变参数基本的使用方法。 可变参数的一个注意事项 我们将传递的参数类型修改成char类型而不改变va_arg会不会出现什么问题呢比如说下面的代码 #includestdio.h #includeWindows.h //num表示可变参数的个数 int FindMax(int num, ...) {va_list arg;va_start(arg, num);//创建变量记录当前最大值int max va_arg(arg, int);for (int i 0; i num - 1; i){int cur va_arg(arg, int);if (max cur){max cur;}}va_end(arg);return max; } int main() {char a a;char b b;char c c;char d d;char e e;char max FindMax(5,a,b,c,d,e);printf(最大的值为%c, max);return 0; }代码的运行结果如下 可以看到当前是没有出现问题的那这是为什么呢为什么我们传递char类型的参数以int类型的方式进行读取却不会出现问题呢原因很简答因为可变参数在传递的过程中会发生以4字节为倍数的向上整形提升如果你传递的参数大小为1字节那么在传参的时候就会自动提升到4个字节如果你传递的参数大小为2个字节那么在传参的时候也会自动的提升到4个字节如果你传递的参数大小为6个字节那么在传参的时候就会自动的提升到8个字节以此类推这也是为什么我们上面传递char类型的数据以int的方式进行读取却没有出错的原因这里我们还有两个方法来进行验证首先就是查看可变参数传递时的汇编指令 上面传递int类型时是采用move指令但是这里采用的确实movsx指令movsx是x86汇编语言中的一种指令用于将一个有符号数扩展到一个更大的寄存器中。它的作用是将一个有符号数从一个小的寄存器或内存位置移动到一个大的寄存器中并将高位扩展为符号位。这个指令可以用来处理有符号数的转移避免出现符号位扩展错误的情况简单的来说就是整形提升。另外一个方法就是通过内存来进行查看首先找到参数num对应的地址 因为参数是从右向左连续传递的上面是低地址下面是高地址并且这里的内存监视是以4个字节为一行来进行显示所以004FAB0下面5行就是对应的参数在内存上的数据 不难发现这5行的内容刚好就是abcde一行就是4个字节所以这也间接的证明了可变参数在传递的时候会发生整形提升的特点那如果我们传递char类型数据以char类型为单位进行读取的话会不会出现问题呢这里我们可以通过下面的代码来尝试一下 void test(int num, ...) {va_list arg;va_start(arg, num);for (int i 0; i num; i){int cur va_arg(arg, char);printf(%c , cur);} } int main() {char a a;char b b;char c c;char d d;char e e;test(5,a,b,c,d,e);return 0; }代码的运行结果如下 答案是没有问题的因为va_arg也会对你传递过来的类型进行整形提升这里我们在可变参数的底层原理给大家讲解那么这就是可变参数的使用方法和特性。 可变参数的底层原理 va_list va_list的作用就是创建一个char类型的指针用来指向可变参数他的底层起始就是char的重命名 va_end va_end的作用就是将va_list创建的变量的值置为空我们来看看这个宏的定义如何 底层是__crt_va_end这个东西也是一个宏我们再来看看他的定义 ap是一个char类型的指针那么这里的意思就是将数字0强制类型转换为char*类型然后再赋值给指针ap也就是将指针ap置为空的意思。 va_start va_start的作用就是初始化指针让其指向可变参数的第一个参数该参数的底层如下 底层套了一个宏__crt_va_start该宏的定义如下 可以看到__crt_va_start的在底层又套了一个宏__crt_va_start_a该宏的定义如下 __crt_va_start_a依靠两个宏来实现第一个AARESSOF的作用就是提取参数的地址 第二个_INTSIZEOF的作用就是计算类型或者变量整形提升之后创建变量所占的空间大小如果n的类型为char那么这个宏计算的结果就是4具体是如何计算的我们后面再说 知道了这两个宏的作用我们再来看__crt_va_start_a的底层意思就是先获取固定参数的地址然后再获取固定参数所占的地址大小最后将两个值相加的结果赋值给ap即可这样ap就指向了可变参数的第一个参数我们可以通过图片再来理解理解 因为最近的变量的类型为int所以_INTSIZEOF计算得到的结果就是4009CFD64加上4之后得到的结果就刚好是009CFD68也就是可变参数第一个参数的地址然后将该地址赋值给ap图片就变成下面这样 这就是初始化的作用和原理。 va_arg va_arg的作用就是根据你传递过来的类型获取指针指向的空间内容并将指针向后移动类型对应的大小比如说一开始指针指向的内容如下 传递的类型为int的话va_arg的作用获取方括号中的内容并将指针指向方括号下面的62那么图片就变成下面这样 va_arg的底层如下 再转换一下便可以看到真正的底层 这里的结构比较复杂首先执行的运算是(ap _INTSIZEOF(t)得到结果的就是让指针ap向下移动t类型整形提升后大小的长度也就是从图片中的a指向了b 表达式都会返回值那么上一步的返回值就是b的地址将这个地址减去_INTSIZEOF(t)就又得到了a的地址但是指针ap指向的依然是b将地址a的类型转换成为t*这样就可以得到方括号中的内容而指针ap依然指向的b 那么这就是va_arg的底层实现。 _INTSIZEOF _INTSIZEOF的底层如下 ((sizeof(n) sizeof(int) - 1) ~(sizeof(int) - 1))为了后面方便表述我们假设sizeof(n)的值是nchar 1short 2, int 4我们在32位平台vs2013下测试sizeof(int)大小是4其他情况我们不考虑_INTSIZEOF(n)的意思计算一个最小数字x满足 xn x%40其实就是一种4字节对齐的方式比如n是1,2,3,4对n进行向 sizeof(int) 的最小整数倍取整也就是4比如n是5,6,7,8对n进行向 sizeof(int) 的最小整数倍取整就是8对于这个公式我们有三步理解 第一步理解4的倍数 既然是4的最小整数倍取整那么本质是x4*mm是具体几倍。对7来讲m就是2对齐的结果就是8而m具体是多少取决于n是多少如果n能整除4那么m就是n/4如果n不能整除4那么m就是n/41 上面是两种情况如何合并成为一种写法呢常见做法是 ( nsizeof(int)-1) )/sizeof(int) - (n4-1)/4 如果n能整除4那么m就是(n4-1)/4-(n3)/4, 3的值无意义会因取整自动消除等价于 n/4如果n不能整除4那么n最大能整除4部分r,1r4 那么m就是 (n4-1)/4-(能整除4部分r3)/4,其中4r37 - 能整除4部分/4 (r3)/4 - n/41。 第二步理解最小4字节对齐数 搞清楚了满足条件最小是几倍问题那么计算一个最小数字x满足 xn x%40就变成了 ((nsizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] - ((n4-1)/4)*4这样就能求出来4字节对齐的数据了其实上面的写法在功能上已经和源代码中的宏等价了。 第三步理解理解源代码中的宏 拿出简洁写法(n4-1)/4* 4设wn4-1, 那么表达式可以变化成为 (w/4)*4,而4就是2^2,w/4,不就相当于右移两位吗再次*4不就相当左移两位吗先右移两位再左移两位最终结果就是最后2个比特位被清空为0需要这么费劲吗w ~3 不香吗所以简洁版(n4-1) ~(4-1)原码版( (sizeof(n) sizeof(int) - 1) ~(sizeof(int) - 1) )无需先/,在*。 可变参数的注意事项 第一 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止这是可以的但是如果你想一开始就访问参数列表中间的参数那是不行的。 第二 参数列表中至少有一个命名参数。如果连一个命名参数都没有就无法使用 va_start 这些宏是无法直接判断实际存在参数的数量如果想要知道可变参数的个数就可以跟我们上面一样传递一个固定的参数用来表示可变参数的数量或者像printf函数一样根据特殊的标记来判断参数的个数比如说printf(hello word:%d %c,3.14,c)我们就可以创建一个字符指针p指向文本信息然后创建一个循环每读取一个字符就进行判断如果不为字符%就直接跳过如果为%就继续判断下一个字符是否为d是否为f等等等如果下一个字符为d我们就可以以int形式提取可变参数如果为f我们就可以以float的形式提取可变参数等等比如说下面的代码 va_list start; va_start(start); while(*p){switch(*p){case %:p;if(*p f) arg va_arg(start, float);if(*p d) arg va_arg(start, int);...} } va_end(start); 那么通过这样的方式我们也能够判断可变参数个数。 第三 这些宏无法判断每个参数的是类型所以我们得自己传递具体的类型。 第四 如果在va_arg 中指定了错误的类型那么其后果是不可预测的。 日志的实现 因为宏的本质就是数字 #define LOG_NORMAL log.txt//运行正常发送的文件 #define LOG_ERR log.error//运行不正常发送的文件#define DEBUG 0 #define NORMAL 1 #define WARNING 2 #define ERROR 3 #define FATAL 4所以我们首先得创建一个函数用来将数字转换成为问题等级的字符 const char * to_levelstr(int level) {switch(level){case DEBUG : return DEBUG;case NORMAL: return NORMAL;case WARNING: return WARNING;case ERROR: return ERROR;case FATAL: return FATAL;default : return nullptr;} }问题内容由问题属性和问题信息组成所以我们先创建一个缓冲区通过snprintf函数将问题属性输出到缓冲区中 void logMessage(int level, const char *format, ...) { // [日志等级] [时间戳/时间] [pid] [messge] // [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败] #define NUM 1024char logprefix[NUM];snprintf(logprefix, sizeof(logprefix), [%s][%ld][pid: %d],to_levelstr(level), (long int)time(nullptr), getpid()); } 再创建一个缓冲区用来记录问题信息因为问题信息存在可变参数所以还得创建一个va_list变量并将其初始化然后就可以使用vsnprintf函数将文本信息和可变参数以特定的形式输出到缓冲区中这里的特殊形式就是%d对应的是整形%f对应的是浮点型等等等就和printf函数一样vsnprintf的参数如下 第一个参数表示往哪个缓冲区输出第二个参数表示大小是多少第三个参数表示输出的内容第四个参数就是可变参数列表那么这里的代码如下 void logMessage(int level, const char *format, ...) { // [日志等级] [时间戳/时间] [pid] [messge] // [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败] #define NUM 1024char logprefix[NUM];snprintf(logprefix, sizeof(logprefix), [%s][%ld][pid: %d],to_levelstr(level), (long int)time(nullptr), getpid());char logcontent[NUM];va_list arg;va_start(arg, format);vsnprintf(logcontent, sizeof(logcontent), format, arg) } ;然后我们就可以根据level的等级选着对应的打开文件将两个缓冲区的内容按照顺序输出到文件中 void logMessage(int level, const char *format, ...) {#define NUM 1024char logprefix[NUM];snprintf(logprefix, sizeof(logprefix), [%s][%ld][pid: %d],to_levelstr(level), (long int)time(nullptr), getpid());char logcontent[NUM];va_list arg;va_start(arg, format);vsnprintf(logcontent, sizeof(logcontent), format, arg);// std::cout logprefix logcontent std::endl;FILE *log fopen(LOG_NORMAL, a);FILE *err fopen(LOG_ERR, a);if(log ! nullptr err ! nullptr){FILE *curr nullptr;if(level DEBUG || level NORMAL || level WARNING) curr log;if(level ERROR || level FATAL) curr err;if(curr) fprintf(curr, %s%s\n, logprefix, logcontent);fclose(log);fclose(err);}那么这就是日志发送函数。 日志的测试 我们可以使用下面的代码来进行测试 int sock3; logMessage(NORMAL, accept a new link success, get new sock: %d, sock); // ? logMessage(DEBUG, accept error, next); logMessage(WARNING, accept error, next); logMessage(FATAL, accept error, next); logMessage(NORMAL, accept error, next);logMessage(NORMAL, accept a new link success, get new sock: %d, sock); // ? logMessage(DEBUG, accept error, next); logMessage(WARNING, accept error, next); logMessage(FATAL, accept error, next); logMessage(NORMAL, accept error, next);logMessage(NORMAL, accept a new link success, get new sock: %d, sock); // ? logMessage(DEBUG, accept error, next); logMessage(WARNING, accept error, next); logMessage(FATAL, accept error, next); logMessage(NORMAL, accept error, next);代码的运行结果如下 log.error中全是FATAL类型的错误 log.txt中全是NORMAL,WARNING,DEBUG类型的错误那么这就本篇文章的全部内容。
http://www.zqtcl.cn/news/921141/

相关文章:

  • 网站规划和建设度假区网站建设方案
  • 做网站前端用什么软件好在线种子资源网
  • 怎样修改网站关键词昌平做网站的公司
  • 网站建设调研文档网站最下面版权模板
  • 建外贸网站有效果吗开发电商平台需要多少钱
  • 成都网站建设维护网页制作价格私活
  • 建设银行网站登陆不上做本地的分类信息网站
  • 公司网站建设哪里实惠网页设计作业百度网盘
  • 如何seo网站挣钱不同企业的网络营销网站
  • 自己做网站有什么用网站怎样设计网址
  • 做任务的网站有那些wordpress链接在哪里
  • 免费建站模板网站招聘网站哪个好
  • 网站建站推广是啥意思高端网站建设浩森宇特
  • 长治电子商务网站建设中国建设银行总行官方网站
  • 整站营销系统厚街镇网站仿做
  • 舆情分析网站wordpress文章聚合
  • 中国建设银行网站在哪上市cpa自己做网站
  • 网站建设服务支持jquery插件 wordpress
  • 最有效的100个营销方法seo工作室
  • wordpress o2o主题嘉兴网站优化联系方式
  • 网站建设最基础的是什么网站怎么做架构
  • 网站底部怎么修改网站服务器是干什么的
  • 网络营销是营销的网络化吗广州推广seo
  • 茌平做网站推广网站刷链接怎么做的
  • 东莞网站优化推广Wordpress的根目录在哪
  • 备案的网站建设书是什么意思跨境电商代运营公司十强
  • 网站建设的功能要求wordpress typo3
  • 深圳网站平台前程无忧招聘网
  • 个人业余做网站怎么弄wordpress子主题修改
  • 深圳营销型网站建设优化做虚拟币网站需要什么手续