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

函数简单传入参数的汇编分析

文章目录

    • 1.演示代码
    • 2.演示代码对应的汇编代码
    • 3.主函数调用部分
    • 完整的主函数栈帧建立
    • 1. 无参数函数调用(完整分析)
    • 2. 单参数函数调用(三种场景对比)
      • 场景1:变量传递
      • 场景2:字面量传递
      • 场景3:表达式传递
    • 3. 双参数函数调用(完整分析)
    • 4. 三参数函数调用(寄存器使用分析)
    • 5. 15参数函数调用(完整栈操作)
    • 6. 函数返回和栈帧销毁
    • 关键技术细节总结:
      • 1. **调用约定特征**
      • 2. **寄存器使用模式**
      • 3. **内存布局**
      • 4. **调试特征**
      • 5. **性能优化点**
  • 传递参数和不传递参数有啥区别,如果从堆栈的角度传递参数和不传递参数有什么区别
    • 1. 无参数函数调用的堆栈变化
    • 2. 有参数函数调用的堆栈变化
      • 单参数例子:
    • 3. 关键区别对比
    • 4. 具体堆栈布局对比
      • 无参数调用时的堆栈帧:
      • 有参数调用时的堆栈帧(以funcTwoParams为例):
    • 5. 从函数内部视角看区别
      • 无参数函数内部访问:
      • 有参数函数内部访问:
    • 6. 性能影响分析
      • 无参数调用:
      • 有参数调用(N个参数):
    • 7. 实际堆栈示例对比
      • 场景1:无参数调用
      • 场景2:双参数调用
    • 总结
  • 4. 函数内部是如何使用这些传入的参数的呢
    • 1. 单个参数函数 `funcOneParam(int a)`
    • 2. 两个参数函数 `funcTwoParams(int a, int b)`
    • 3. 三个参数函数 `funcThreeParams(int a, int b, int c)`
    • 4. 多个参数函数 `funcManyParams` (15个参数)
    • 关键观察:

1.演示代码

#include <iostream>// 无参数函数
void funcNoParams() {std::cout << "无参数函数被调用" << std::endl;
}// 单个参数函数
void funcOneParam(int a) {std::cout << "单参数函数: " << a << std::endl;
}// 两个参数函数
void funcTwoParams(int a, int b) {std::cout << "双参数函数: " << a << ", " << b << std::endl;
}// 三个参数函数
void funcThreeParams(int a, int b, int c) {std::cout << "三参数函数: " << a << ", " << b << ", " << c << std::endl;
}// 多个参数函数(15个参数)
void funcManyParams(int a1, int a2, int a3, int a4, int a5,int a6, int a7, int a8, int a9, int a10,int a11, int a12, int a13, int a14, int a15) {std::cout << "15参数函数调用:" << std::endl;std::cout << "前5个参数: " << a1 << ", " << a2 << ", " << a3 << ", " << a4 << ", " << a5 << std::endl;std::cout << "中5个参数: " << a6 << ", " << a7 << ", " << a8 << ", " << a9 << ", " << a10 << std::endl;std::cout << "后5个参数: " << a11 << ", " << a12 << ", " << a13 << ", " << a14 << ", " << a15 << std::endl;
}int main() {//std::cout << "=== 函数参数传递对比演示 ===" << std::endl;// 演示无参数函数调用//std::cout << "\n--- 无参数函数调用 ---" << std::endl;funcNoParams();// 演示单个参数函数调用//std::cout << "\n--- 单参数函数调用 ---" << std::endl;int x = 10;funcOneParam(x);           // 使用变量funcOneParam(20);          // 使用字面量funcOneParam(x + 5);       // 使用表达式// 演示两个参数函数调用//std::cout << "\n--- 双参数函数调用 ---" << std::endl;int y = 30;funcTwoParams(x, y);       // 使用两个变量funcTwoParams(40, 50);     // 使用两个字面量funcTwoParams(x, y * 2);   // 混合使用变量和表达式// 演示三个参数函数调用//std::cout << "\n--- 三参数函数调用 ---" << std::endl;int z = 60;funcThreeParams(x, y, z);  // 使用三个变量funcThreeParams(70, 80, 90); // 使用三个字面量// 演示多个参数函数调用(使用变量)//std::cout << "\n--- 多参数函数调用(使用变量)---" << std::endl;int var1 = 100, var2 = 200, var3 = 300, var4 = 400, var5 = 500;int var6 = 600, var7 = 700, var8 = 800, var9 = 900, var10 = 1000;int var11 = 1100, var12 = 1200, var13 = 1300, var14 = 1400, var15 = 1500;funcManyParams(var1, var2, var3, var4, var5,var6, var7, var8, var9, var10,var11, var12, var13, var14, var15);return 0;
}

2.演示代码对应的汇编代码

3.主函数调用部分


int main() {
00412444  in          al,dx  
00412445  cwde  
00412446  add         dword ptr [eax],eax  
00412448  add         byte ptr [ebx+56h],dl  
0041244B  push        edi  
0041244C  lea         edi,[ebp-0D8h]  
00412452  mov         ecx,36h  
00412457  mov         eax,0CCCCCCCCh  
0041245C  rep stos    dword ptr es:[edi]  
0041245E  mov         ecx,offset _C66D3399_simple_cpp@cpp (0420077h)  
00412463  call        @__CheckForDebuggerJustMyCode@4 (04113C5h)  
00412468  nop  //std::cout << "=== 函数参数传递对比演示 ===" << std::endl;// 演示无参数函数调用//std::cout << "\n--- 无参数函数调用 ---" << std::endl;funcNoParams();
00412469  call        funcNoParams (0411532h)  
0041246E  nop  // 演示单个参数函数调用//std::cout << "\n--- 单参数函数调用 ---" << std::endl;int x = 10;
0041246F  mov         dword ptr [x],0Ah  funcOneParam(x);           // 使用变量
00412476  mov         eax,dword ptr [x]  
00412479  push        eax  
0041247A  call        funcOneParam (041152Dh)  
0041247F  add         esp,4  funcOneParam(20);          // 使用字面量
00412482  push        14h  
00412484  call        funcOneParam (041152Dh)  
00412489  add         esp,4  funcOneParam(x + 5);       // 使用表达式
0041248C  mov         eax,dword ptr [x]  
0041248F  add         eax,5  
00412492  push        eax  
00412493  call        funcOneParam (041152Dh)  
00412498  add         esp,4  // 演示两个参数函数调用//std::cout << "\n--- 双参数函数调用 ---" << std::endl;int y = 30;
0041249B  mov         dword ptr [y],1Eh  funcTwoParams(x, y);       // 使用两个变量
004124A2  mov         eax,dword ptr [y]  
004124A5  push        eax  
004124A6  mov         ecx,dword ptr [x]  
004124A9  push        ecx  
004124AA  call        funcTwoParams (0411528h)  
004124AF  add         esp,8  funcTwoParams(40, 50);     // 使用两个字面量
004124B2  push        32h  
004124B4  push        28h  
004124B6  call        funcTwoParams (0411528h)  
004124BB  add         esp,8  funcTwoParams(x, y * 2);   // 混合使用变量和表达式
004124BE  mov         eax,dword ptr [y]  
004124C1  shl         eax,1  
004124C3  push        eax  
004124C4  mov         ecx,dword ptr [x]  
004124C7  push        ecx  
004124C8  call        funcTwoParams (0411528h)  
004124CD  add         esp,8  // 演示三个参数函数调用//std::cout << "\n--- 三参数函数调用 ---" << std::endl;int z = 60;
004124D0  mov         dword ptr [z],3Ch  funcThreeParams(x, y, z);  // 使用三个变量
004124D7  mov         eax,dword ptr [z]  
004124DA  push        eax  
004124DB  mov         ecx,dword ptr [y]  
004124DE  push        ecx  
004124DF  mov         edx,dword ptr [x]  
004124E2  push        edx  
004124E3  call        funcThreeParams (041153Ch)  
004124E8  add         esp,0Ch  funcThreeParams(70, 80, 90); // 使用三个字面量
004124EB  push        5Ah  
004124ED  push        50h  
004124EF  push        46h  
004124F1  call        funcThreeParams (041153Ch)  
004124F6  add         esp,0Ch  // 演示多个参数函数调用(使用变量)//std::cout << "\n--- 多参数函数调用(使用变量)---" << std::endl;int var1 = 100, var2 = 200, var3 = 300, var4 = 400, var5 = 500;
004124F9  mov         dword ptr [var1],64h  
00412500  mov         dword ptr [var2],0C8h  
00412507  mov         dword ptr [var3],12Ch  
0041250E  mov         dword ptr [var4],190h  
00412515  mov         dword ptr [var5],1F4h  int var6 = 600, var7 = 700, var8 = 800, var9 = 900, var10 = 1000;
0041251C  mov         dword ptr [var6],258h  
00412523  mov         dword ptr [var7],2BCh  
0041252A  mov         dword ptr [var8],320h  
00412531  mov         dword ptr [var9],384h  
0041253B  mov         dword ptr [var10],3E8h  int var11 = 1100, var12 = 1200, var13 = 1300, var14 = 1400, var15 = 1500;
00412545  mov         dword ptr [var11],44Ch  
0041254F  mov         dword ptr [var12],4B0h  
00412559  mov         dword ptr [var13],514h  
00412563  mov         dword ptr [var14],578h  
0041256D  mov         dword ptr [var15],5DCh  funcManyParams(var1, var2, var3, var4, var5,
00412577  mov         eax,dword ptr [var15]  
0041257D  push        eax  
0041257E  mov         ecx,dword ptr [var14]  
00412584  push        ecx  
00412585  mov         edx,dword ptr [var13]  
0041258B  push        edx  
0041258C  mov         eax,dword ptr [var12]  
00412592  push        eax  
00412593  mov         ecx,dword ptr [var11]  
00412599  push        ecx  
0041259A  mov         edx,dword ptr [var10]  
004125A0  push        edx  
004125A1  mov         eax,dword ptr [var9]  
004125A7  push        eax  
004125A8  mov         ecx,dword ptr [var8]  
004125AB  push        ecx  
004125AC  mov         edx,dword ptr [var7]  
004125AF  push        edx  
004125B0  mov         eax,dword ptr [var6]  
004125B3  push        eax  
004125B4  mov         ecx,dword ptr [var5]  
004125B7  push        ecx  
004125B8  mov         edx,dword ptr [var4]  
004125BB  push        edx  
004125BC  mov         eax,dword ptr [var3]  
004125BF  push        eax  
004125C0  mov         ecx,dword ptr [var2]  
004125C3  push        ecx  
004125C4  mov         edx,dword ptr [var1]  
004125C7  push        edx  
004125C8  call        funcManyParams (0411537h)  
004125CD  add         esp,3Ch  var6, var7, var8, var9, var10,var11, var12, var13, var14, var15);return 0;
004125D0  xor         eax,eax  
}
004125D2  pop         edi  
004125D3  pop         esi  
004125D4  pop         ebx  
004125D5  add         esp,198h  
004125DB  cmp         ebp,esp  
004125DD  call        __RTC_CheckEsp (04112C1h)  
004125E2  mov         esp,ebp  
004125E4  pop         ebp  
004125E5  ret  

您说得对,我的分析确实不够完整。让我提供更全面的分析,包括函数调用前后的完整栈帧管理和所有细节:

完整的主函数栈帧建立

int main() {
00412444  in          al,dx  
00412445  cwde  
00412446  add         dword ptr [eax],eax  
00412448  add         byte ptr [ebx+56h],dl  
0041244B  push        edi  
0041244C  lea         edi,[ebp-0D8h]  
00412452  mov         ecx,36h  
00412457  mov         eax,0CCCCCCCCh  
0041245C  rep stos    dword ptr es:[edi]  
0041245E  mov         ecx,offset _C66D3399_simple_cpp@cpp (0420077h)  
00412463  call        @__CheckForDebuggerJustMyCode@4 (04113C5h)  
00412468  nop  

详细分析

  • push edi - 保存EDI寄存器
  • lea edi,[ebp-0D8h] - 计算局部变量区域的起始地址
  • mov ecx,36h - 设置循环次数(54次)
  • mov eax,0CCCCCCCCh - 调试模式的填充值(未初始化内存)
  • rep stos - 用0xCC填充局部变量空间(调试特征)
  • 调用调试检查函数

1. 无参数函数调用(完整分析)

funcNoParams();
00412469  call        funcNoParams (0411532h)  
0041246E  nop  

完整调用过程

  • call 指令:将返回地址(0041246E)压栈,跳转到函数
  • 函数执行完毕后返回到 nop 指令
  • 无参数,无栈调整

2. 单参数函数调用(三种场景对比)

场景1:变量传递

int x = 10;
0041246F  mov         dword ptr [x],0Ah  
funcOneParam(x);
00412476  mov         eax,dword ptr [x]  ; 从内存加载x到eax
00412479  push        eax               ; 参数压栈
0041247A  call        funcOneParam (041152Dh)  
0041247F  add         esp,4             ; 调用者清理栈(1参数=4字节)

场景2:字面量传递

funcOneParam(20);
00412482  push        14h               ; 直接压入字面量20(0x14)
00412484  call        funcOneParam (041152Dh)  
00412489  add         esp,4  

场景3:表达式传递

funcOneParam(x + 5);
0041248C  mov         eax,dword ptr [x]  ; 加载x
0041248F  add         eax,5             ; 计算表达式(x+5)
00412492  push        eax               ; 结果压栈
00412493  call        funcOneParam (041152Dh)  
00412498  add         esp,4  

3. 双参数函数调用(完整分析)

int y = 30;
0041249B  mov         dword ptr [y],1Eh  funcTwoParams(x, y);
004124A2  mov         eax,dword ptr [y]  ; 加载第二个参数y
004124A5  push        eax               ; 先压入y(从右向左)
004124A6  mov         ecx,dword ptr [x]  ; 加载第一个参数x
004124A9  push        ecx               ; 后压入x
004124AA  call        funcTwoParams (0411528h)  
004124AF  add         esp,8             ; 清理8字节(2参数)

4. 三参数函数调用(寄存器使用分析)

int z = 60;
004124D0  mov         dword ptr [z],3Ch  funcThreeParams(x, y, z);
004124D7  mov         eax,dword ptr [z]  ; 使用eax加载第三个参数
004124DA  push        eax               ; 压入z
004124DB  mov         ecx,dword ptr [y]  ; 使用ecx加载第二个参数
004124DE  push        ecx               ; 压入y
004124DF  mov         edx,dword ptr [x]  ; 使用edx加载第一个参数
004124E2  push        edx               ; 压入x
004124E3  call        funcThreeParams (041153Ch)  
004124E8  add         esp,0Ch           ; 清理12字节(3参数)

5. 15参数函数调用(完整栈操作)

funcManyParams(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, var11, var12, var13, var14, var15);
00412577  mov         eax,dword ptr [var15]  ;15个参数
0041257D  push        eax
0041257E  mov         ecx,dword ptr [var14]  ;14个参数  
00412584  push        ecx
; ... 中间13个参数的push操作 ...
004125C4  mov         edx,dword ptr [var1]   ;1个参数
004125C7  push        edx
004125C8  call        funcManyParams (0411537h)  
004125CD  add         esp,3Ch           ; 清理60字节(15×4字节)

6. 函数返回和栈帧销毁

return 0;
004125D0  xor         eax,eax           ; 返回值0
}
004125D2  pop         edi               ; 恢复EDI
004125D3  pop         esi               ; 恢复ESI  
004125D4  pop         ebx               ; 恢复EBX
004125D5  add         esp,198h          ; 调整栈指针,释放局部变量空间
004125DB  cmp         ebp,esp           ; 检查栈平衡
004125DD  call        __RTC_CheckEsp (04112C1h)  ; 运行时栈检查
004125E2  mov         esp,ebp           ; 恢复栈指针
004125E4  pop         ebp               ; 恢复基址指针
004125E5  ret                          ; 返回

关键技术细节总结:

1. 调用约定特征

  • cdecl调用约定:参数从右向左压栈,调用者清理栈
  • 栈清理方式add esp, N(N=参数个数×4)

2. 寄存器使用模式

  • 参数传递:全部使用栈,未使用寄存器传参
  • 临时存储:eax, ecx, edx 用于加载参数值
  • 保存寄存器:ebx, esi, edi 在函数开始时保存

3. 内存布局

栈顶 → [参数n] ... [参数2] [参数1] [返回地址] [保存的ebp] [局部变量]

4. 调试特征

  • 0xCCCCCCCC 填充:未初始化内存的调试标记
  • __CheckForDebuggerJustMyCode:调试器检查
  • __RTC_CheckEsp:运行时栈检查

5. 性能优化点

  • 字面量直接压栈,避免内存加载
  • 表达式在压栈前计算完成
  • 寄存器重用优化(不同参数使用不同寄存器)

这个完整的分析展示了x86架构下C++函数调用的完整机制,包括参数传递、栈管理和调试支持的所有细节。

传递参数和不传递参数有啥区别,如果从堆栈的角度传递参数和不传递参数有什么区别

从堆栈角度分析,传递参数和不传递参数有本质区别。让我详细对比:

1. 无参数函数调用的堆栈变化

funcNoParams();
00412469  call        funcNoParams (0411532h)  
0041246E  nop  

堆栈变化

调用前堆栈: [返回地址] [保存的ebp] [局部变量] ...
↓ call指令执行
调用中堆栈: [新的返回地址(0041246E)] [旧的返回地址] [保存的ebp] [局部变量] ...↑栈顶下移4字节,只压入返回地址

2. 有参数函数调用的堆栈变化

单参数例子:

funcOneParam(x);
00412476  mov eax,dword ptr [x]  
00412479  push eax              ; 参数压栈
0041247A  call funcOneParam     ; 返回地址压栈
0041247F  add esp,4             ; 清理参数

堆栈变化

调用前堆栈: [返回地址] [保存的ebp] [局部变量] ...
↓ push eax[参数x] [返回地址] [保存的ebp] [局部变量] ...
↓ call指令执行  [新的返回地址] [参数x] [旧的返回地址] [保存的ebp] [局部变量] ...↑栈顶下移8字节(参数4+返回地址4

3. 关键区别对比

方面无参数调用有参数调用
栈空间占用只压入返回地址(4字节)返回地址+所有参数大小
栈指针调整call前后esp-4push参数时esp-N,call时esp-4,清理时esp+N
内存访问无额外内存访问需要访问参数所在内存位置
执行时间较短较长(与参数数量成正比)
堆栈布局简单复杂,需要维护参数区

4. 具体堆栈布局对比

无参数调用时的堆栈帧:

高地址 → [调用者的栈帧][main函数的返回地址][保存的ebp]        ← ebp指向这里[局部变量x,y,z...] ← esp指向这里
低地址 → [空栈空间]

调用funcNoParams时:

高地址 → [调用者的栈帧][main函数的返回地址][保存的ebp]        [局部变量x,y,z...] [funcNoParams返回地址(0041246E)] ← esp指向这里
低地址 → [空栈空间]

有参数调用时的堆栈帧(以funcTwoParams为例):

调用前:

高地址 → [调用者的栈帧][main函数的返回地址][保存的ebp]        [局部变量x,y,z...] ← esp指向这里
低地址 → [空栈空间]

调用过程中:

高地址 → [调用者的栈帧][main函数的返回地址][保存的ebp]        [局部变量x,y,z...] [参数y]           ← push eax (y)[参数x]           ← push ecx (x)  [返回地址]         ← call指令,esp指向这里
低地址 → [空栈空间]

5. 从函数内部视角看区别

无参数函数内部访问:

void funcNoParams() {// 只能访问自己的局部变量// 无法通过参数访问调用者的数据
}

有参数函数内部访问:

void funcTwoParams(int a, int b) {// 可以通过ebp+8访问第一个参数a// 可以通过ebp+12访问第二个参数b  // 汇编中通常:a=[ebp+8], b=[ebp+12]
}

函数内部的堆栈视角

高地址 → [调用者的栈帧][返回地址]         ← ebp+4[保存的ebp]        ← ebp指向这里[局部变量]         ← ebp-4, ebp-8, ...[参数1]           ← ebp+8  (第一个参数)[参数2]           ← ebp+12 (第二个参数)[参数...]         ← ebp+16, ...
低地址 → [空栈空间]         ← esp指向这里

6. 性能影响分析

无参数调用:

  • 栈操作:1次call(隐含1次push)
  • 内存访问:无额外内存访问
  • 指令数:1条call指令

有参数调用(N个参数):

  • 栈操作:N次push + 1次call(隐含1次push)+ 1次add esp
  • 内存访问:N次参数加载 + N次push写入
  • 指令数:约3N+2条指令

7. 实际堆栈示例对比

场景1:无参数调用

调用前: ESP = 0x0018FF34
调用中: ESP = 0x0018FF30  (压入返回地址)
堆栈内容: [0x0018FF30: 0041246E] ← 返回地址

场景2:双参数调用

调用前: ESP = 0x0018FF34
push y:  ESP = 0x0018FF30, [0x0018FF30: 30] ← 参数y
push x:  ESP = 0x0018FF2C, [0x0018FF2C: 10] ← 参数x  
call:    ESP = 0x0018FF28, [0x0018FF28: 004124AF] ← 返回地址
清理后:  ESP = 0x0018FF34  (恢复原状)

总结

从堆栈角度看,传递参数的本质是在调用函数前,在栈上为被调用函数准备数据空间。这种区别导致:

  1. 栈空间占用:有参数调用需要更多栈空间
  2. 执行开销:有参数调用有额外的push/pop操作
  3. 内存访问:有参数调用需要访问参数内存位置
  4. 代码复杂度:有参数调用需要维护参数传递协议

这就是为什么在性能敏感的场景中,应该尽量减少参数数量,或者使用寄存器传递参数(如x64架构的fastcall约定)。

4. 函数内部是如何使用这些传入的参数的呢

从你提供的汇编代码可以看出,在x86架构下使用__cdecl调用约定时,函数内部访问参数的方式。让我详细分析每种场景:

1. 单个参数函数 funcOneParam(int a)

参数访问方式:

mov eax, dword ptr [a]        ; 从栈中读取参数a的值到eax寄存器
mov dword ptr [result], eax   ; 将eax的值存入局部变量result

栈帧结构:

[ebp+8]  - 参数a
[ebp+4]  - 返回地址
[ebp]    - 保存的ebp
[ebp-4]  - 局部变量result

2. 两个参数函数 funcTwoParams(int a, int b)

参数访问方式:

mov eax, dword ptr [a]        ; 读取第一个参数a
add eax, dword ptr [b]        ; 加上第二个参数b
mov dword ptr [result], eax   ; 存储结果

栈帧结构:

[ebp+12] - 参数b
[ebp+8]  - 参数a  
[ebp+4]  - 返回地址
[ebp]    - 保存的ebp
[ebp-4]  - 局部变量result

3. 三个参数函数 funcThreeParams(int a, int b, int c)

参数访问方式:

mov eax, dword ptr [a]        ; 读取参数a
add eax, dword ptr [b]        ; 加上参数b
add eax, dword ptr [c]        ; 加上参数c
mov dword ptr [result], eax   ; 存储结果

栈帧结构:

[ebp+16] - 参数c
[ebp+12] - 参数b
[ebp+8]  - 参数a
[ebp+4]  - 返回地址
[ebp]    - 保存的ebp
[ebp-4]  - 局部变量result

4. 多个参数函数 funcManyParams (15个参数)

参数访问方式:

mov eax, dword ptr [a1]       ; 读取a1
add eax, dword ptr [a2]       ; 加上a2
add eax, dword ptr [a3]       ; 加上a3
; ... 继续加上a4到a15
mov dword ptr [result], eax   ; 存储最终结果

栈帧结构:

[ebp+64] - 参数a15 (最后一个参数)
...
[ebp+12] - 参数a2
[ebp+8]  - 参数a1 (第一个参数)
[ebp+4]  - 返回地址
[ebp]    - 保存的ebp
[ebp-4]  - 局部变量result

关键观察:

  1. 参数传递顺序:在__cdecl调用约定下,参数从右向左压栈,所以最后一个参数在栈的最高地址。

  2. 参数访问方式:所有参数都通过[ebp+offset]的方式从栈中访问,偏移量计算为:

    • 第一个参数:[ebp+8]
    • 第二个参数:[ebp+12]
    • 第n个参数:[ebp+8+4*(n-1)]
  3. 局部变量存储:局部变量result存储在栈帧的[ebp-4]位置。

  4. 栈帧建立:每个函数开始都执行标准序言:

    push ebp
    mov ebp, esp
    sub esp, 0CCh  ; 为局部变量分配空间
    
  5. 栈帧清理:函数结束时恢复栈指针:

 mov esp, ebppop ebp

这种参数传递方式确保了函数能够正确访问调用者传递的所有参数,无论参数数量多少。

http://www.dtcms.com/a/435168.html

相关文章:

  • 怎样做已有网站的编辑维护大沥九江网站制作
  • 自己网站首页如何设置网站托管维护方案
  • 安卓基础组件024-底部导航栏
  • 【ROS2学习笔记】话题通信篇:话题通信项目实践——系统状态监测与可视化工具
  • 苏州门户网站平台推广员
  • ICT 数字测试原理 4 --电源监控电路(PMC)
  • 网站内页要不要加上关键词和描述广告型网站怎么做的
  • 深圳市建设局工程交易中心网站贵州 网站建设
  • 【C/C++】 函数形参—指针传递
  • 门限签名与多方安全计算(MPC)
  • 东莞公司高端网站建设高大上网站
  • 基于websocket的多用户网页五子棋(一)
  • PCA 主成分分析:数据世界的 “旅行清单整理师”—— 从 30 维杂乱到 2 维清晰的诗意降维
  • wordpress兼容mipseo加盟代理
  • 台州网站排名优化费用网站建设设计风格描述
  • 利用 ZoneABC 免费域名 零成本接入 Cloudflare 企业版 CDN
  • 企业网站建设费用属于什么科目dw软件网站建设教程
  • Streamlit:基础入门——零基础搭建第一个 Web 应用
  • Netty粘包和半包问题产生的原因和解决方案
  • 【小沐学GIS】基于C++绘制地形DEM(OpenGL、Terrain、TIFF、hgt)第十二期
  • 怎么搭建本地网站外贸营销工具
  • MySQL常用命令全攻略
  • 郑州市网站和公众号建设长沙公积金网站怎么做异动
  • 平面设计有什么网站wordpress 汽车模板下载
  • 珠宝首饰网站开发郑州微盟网站建设公司
  • 网站建设毕业设计指导老师意见什么网站可以做设计
  • 想学做网站要去哪里学健身网站开发过程中遇到的麻烦
  • 网站建设方案设计书参考西安最新消息今天
  • 代做道具网站备案网站可以做论坛么
  • SnapTube v7.46.1.74675101 | 免登下载油管4K视频,支持上百个网站的视频和音乐下载