首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
Java
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
309
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
1
篇与
的结果
2016-04-11
C语言循环的实现
在C语言中采用3中语法来实现循环,它们分别是while、for、do while,本文将分别说明这三种循环的实现,并对它们的运行效率进行比较。do while首先来看do while的实现:下面是简单的代码:int nCount = 0; int nMax = 10; do { nCount++; } while (nCount < nMax); return 0; 下面对应的是它的汇编代码:9: int nCount = 0; 00401268 mov dword ptr [ebp-4],0 10: int nMax = 10; 0040126F mov dword ptr [ebp-8],0Ah 11: do 12: { 13: nCount++; 00401276 mov eax,dword ptr [ebp-4] 00401279 add eax,1 0040127C mov dword ptr [ebp-4],eax 14: } while (nCount < nMax); 0040127F mov ecx,dword ptr [ebp-4];exc = nCount 00401282 cmp ecx,dword ptr [ebp-8];比较nCount 和 nMax的值 00401285 jl main+26h (00401276);跳转到循环体中 15: return 0; 00401287 xor eax,eax在汇编代码中首先执行了一次循环体中的操作,然后判断,当条件满足时会跳转回循环体,然后再次执行,当条件不满足时会接着执行后面的语句。这个过程可以用goto来模拟: int nCount = 0; int nMax = 10; __WHILE: nCount++; if(nCount < nMax) goto __WHILE;while循环不同于do while的先执行再比较,while采取的是先比较再循环的方式,下面是一个while的例子: int nCount = 0; int nMax = 10; while (nCount < nMax) { nCount++; }00401268 mov dword ptr [ebp-4],0 10: int nMax = 10; 0040126F mov dword ptr [ebp-8],0Ah 11: while (nCount < nMax) 00401276 mov eax,dword ptr [ebp-4] 00401279 cmp eax,dword ptr [ebp-8] 0040127C jge main+39h (00401289) 12: { 13: nCount++; 0040127E mov ecx,dword ptr [ebp-4] 00401281 add ecx,1 00401284 mov dword ptr [ebp-4],ecx 14: } 00401287 jmp main+26h (00401276) 15: return 0; 00401289 xor eax,eax 从汇编代码上可以看出,执行while循环时会有两次跳转,当条件不满足时会执行一次跳转,跳转到循环体外,而条件满足,执行完一次循环后,会再次跳转到循环体中,再次进行比较。相比于do while来说,while执行了两次跳转,效率相对较低。for 循环for循环是首先进行初始化操作然后进行比较,条件满足时执行循环,再将循环变量递增,最后再次比较,执行循环或者跳出。下面是for的简单例子: int nMax = 10; for (int i = 0; i < nMax; i++) { printf("%d\n", i); }下面是它对应的汇编代码:9: int nMax = 10; 00401268 mov dword ptr [ebp-4],0Ah 10: for (int i = 0; i < nMax; i++) 0040126F mov dword ptr [ebp-8],0 ;初始化循环变量 00401276 jmp main+31h (00401281);跳转到比较操作处 00401278 mov eax,dword ptr [ebp-8] 0040127B add eax,1 0040127E mov dword ptr [ebp-8],eax;这三句话实现的是循环变量自增操作 00401281 mov ecx,dword ptr [ebp-8];ecx = i 00401284 cmp ecx,dword ptr [ebp-4];比较ecx与i 00401287 jge main+4Ch (0040129c);跳转到循环体外 11: { 12: printf("%d\n", i); 00401289 mov edx,dword ptr [ebp-8] 0040128C push edx 0040128D push offset string "%d\n" (0042e01c) 00401292 call printf (00401540) 00401297 add esp,8 13: } 0040129A jmp main+28h (00401278);跳转到i++位置 14: return 0; 0040129C xor eax,eax从上面的汇编代码可以看出for循环的效率最低,它经过了3次跳转,生成对应的汇编代码上,初始化操作后面紧接着是循环变量自增操作,所以首先在完成初始化后会进行一次跳转,跳转到判断,然后根据判断条件再次跳转或者接着执行循环体,最后当循环完成后会再次跳转到循环变量自增的位置,同样采用goto语句来模拟这个操作: int nMax = 10; int i = 0; goto __CMP; __ADD: i++; __CMP: if (i >= nMax) { goto __RETURN; } __LOOP: printf("%d\n", i); goto __ADD; __RETURN: return 0;continue语句continue用于结束这次循环进入下一次循环,下面采用最复杂的for循环来说明continue语句:int nMax = 10; int i = 0; for(;i < nMax; i++) { if (i == 6) { continue; } }下面是它对应的汇编代码:00401268 mov dword ptr [ebp-4],0Ah 10: int i = 0; 0040126F mov dword ptr [ebp-8],0 11: for(;i < nMax; i++) 00401276 jmp main+31h (00401281) 00401278 mov eax,dword ptr [ebp-8] 0040127B add eax,1 0040127E mov dword ptr [ebp-8],eax 00401281 mov ecx,dword ptr [ebp-8] 00401284 cmp ecx,dword ptr [ebp-4] 00401287 jge main+43h (00401293) 12: { 13: if (i == 6) 00401289 cmp dword ptr [ebp-8],6; 0040128D jne main+41h (00401291);条件不满足组跳转到循环结束处 14: { 15: continue; 0040128F jmp main+28h (00401278) 16: } 17: } 00401291 jmp main+28h (00401278) 18: return 0; 00401293 xor eax,eax 从上面的汇编代码可以看到,continue语句也是一个跳转语句,它会直接跳转到循环体的开始位置。对于for来说相对特殊一些(我觉得循环变量自增并不属于循环体),由于第一次进入循环时并没有执行循环变量自增,所以它会跳转到循环变量自增的位置,其他则直接到循环开始处。慎用gotogoto 语句就像汇编中的 jmp 一样,是直接跳转到对应的标识位置,从上面我们使用goto来模拟各种循环来看,goto语句的可读性不强,而且有可能跳过变量的初始化等过程造成一些难以察觉的问题,但有些时候goto确实好用,例如在写socket或者其他需要清理资源的代码时,goto可以显著的增加程序的可读性并且也会减少相关代码的编写,例如一个典型的服务端socket例子#include <winsock2.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "ws2_32.lib") // Winsock Library #define PORT 8080 #define BUFFER_SIZE 1024 int main() { WSADATA wsaData; SOCKET serverSocket, clientSocket; struct sockaddr_in serverAddr, clientAddr; int addrLen = sizeof(clientAddr); char buffer[BUFFER_SIZE]; // 初始化 Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("Failed to initialize Winsock. Error Code: %d\n", WSAGetLastError()); return EXIT_FAILURE; } // 创建 socket serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == INVALID_SOCKET) { printf("Could not create socket. Error Code: %d\n", WSAGetLastError()); WSACleanup(); return EXIT_FAILURE; } // 设置服务器地址结构 serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的接口 serverAddr.sin_port = htons(PORT); // 转换为网络字节序 // 绑定 socket if (bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { printf("Bind failed. Error Code: %d\n", WSAGetLastError()); closesocket(serverSocket); WSACleanup(); return EXIT_FAILURE; } // 开始监听 if (listen(serverSocket, 3) == SOCKET_ERROR) { printf("Listen failed. Error Code: %d\n", WSAGetLastError()); closesocket(serverSocket); WSACleanup(); return EXIT_FAILURE; } printf("Server is listening on port %d...\n", PORT); // 接受客户端连接 clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddr, &addrLen); if (clientSocket == INVALID_SOCKET) { printf("Accept failed. Error Code: %d\n", WSAGetLastError()); closesocket(serverSocket); WSACleanup(); return EXIT_FAILURE; } printf("Client connected.\n"); // 发送消息给客户端 const char *message = "Hello from server!"; send(clientSocket, message, strlen(message), 0); // 关闭 sockets closesocket(clientSocket); closesocket(serverSocket); WSACleanup(); return EXIT_SUCCESS; }中间有好几次执行了closesocket、以及最后的WSACleanup操作、前面每一步出错都要写一次这些清理资源的操作。如果使用goto将会简单的多#include <winsock2.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "ws2_32.lib") // Winsock Library #define PORT 8080 #define BUFFER_SIZE 1024 int main() { WSADATA wsaData; SOCKET serverSocket, clientSocket; struct sockaddr_in serverAddr, clientAddr; int addrLen = sizeof(clientAddr); char buffer[BUFFER_SIZE]; int err = EXIT_SUCCESS; // 初始化 Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("Failed to initialize Winsock. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; goto __CLEANUP; } // 创建 socket serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == INVALID_SOCKET) { printf("Could not create socket. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; goto __CLEANUP; } // 设置服务器地址结构 serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的接口 serverAddr.sin_port = htons(PORT); // 转换为网络字节序 // 绑定 socket if (bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { printf("Bind failed. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; goto __CLEANUP; } // 开始监听 if (listen(serverSocket, 3) == SOCKET_ERROR) { printf("Listen failed. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; goto __CLEANUP; } printf("Server is listening on port %d...\n", PORT); // 接受客户端连接 clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddr, &addrLen); if (clientSocket == INVALID_SOCKET) { printf("Accept failed. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; goto __CLEANUP; } printf("Client connected.\n"); // 发送消息给客户端 const char *message = "Hello from server!"; send(clientSocket, message, strlen(message), 0); // 关闭 sockets __CLEANUP: if(clientSocket != INVALID_SOCKET) { closesocket(clientSocket) } if(serverSocket != INVALID_SOCKET) { closesocket(serverSocket); } WSACleanup(); return err; }如果在不允许使用goto的情况下,可以考虑使用 do while 来模拟这种情况,上面的代码可以修改为#include <winsock2.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "ws2_32.lib") // Winsock Library #define PORT 8080 #define BUFFER_SIZE 1024 int main() { WSADATA wsaData; SOCKET serverSocket, clientSocket; struct sockaddr_in serverAddr, clientAddr; int addrLen = sizeof(clientAddr); char buffer[BUFFER_SIZE]; int err = EXIT_SUCCESS; do{ // 初始化 Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("Failed to initialize Winsock. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; break; } // 创建 socket serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == INVALID_SOCKET) { printf("Could not create socket. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; break; } // 设置服务器地址结构 serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的接口 serverAddr.sin_port = htons(PORT); // 转换为网络字节序 // 绑定 socket if (bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { printf("Bind failed. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; break; } // 开始监听 if (listen(serverSocket, 3) == SOCKET_ERROR) { printf("Listen failed. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; break; } printf("Server is listening on port %d...\n", PORT); // 接受客户端连接 clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddr, &addrLen); if (clientSocket == INVALID_SOCKET) { printf("Accept failed. Error Code: %d\n", WSAGetLastError()); err = EXIT_FAILURE; break; } printf("Client connected.\n"); // 发送消息给客户端 const char *message = "Hello from server!"; send(clientSocket, message, strlen(message), 0); }while (FALSE); // 关闭 sockets if(clientSocket != INVALID_SOCKET) { closesocket(clientSocket) } if(serverSocket != INVALID_SOCKET) { closesocket(serverSocket); } WSACleanup(); return err; }这里的while不是为了循环,而是利用了do while 无论如何都会先执行循环体中代码的特性,只执行一次上述主体代码,利用break来跳转到最后的清理模块,实现与goto 类似的效果。使用goto 的方案比do while的方案要显得简洁易懂,goto使用的好,也能使得程序简单易懂。具体使用哪种方案是个见仁见智的事情,看个人喜好。如果遇上公司要求不能使用 goto,那么就可以采用do while的实现方案
2016年04月11日
16 阅读
0 评论
0 点赞