C/C++中字符编码与指针应用全解析

字符字符串

字符字符串在c\c++中是由"\0"作为字符串结束标记。

字符的编码

编码格式只分为两种:ascii和unicode。Ascii只占1字节,由0-255之间的数字组成。

ASCII码对照表,ASCII码一览表(非常详细) - C语言中文网 (biancheng.net)

使用char定义ascii编码格式的字符,使用w_char定义Unicode编码格式的字符。

W_char中保存ascii编码的字符,不足位补0。

字符串的存储方式

  1. 保存总长度

优点:获取字符串长度不需要遍历

缺点:要保存字符串长度n(额外开销),字符串不能超过长度范围。

  1. 结束符

优点:没有长度作为开销,可以随时结束。

缺点:需要遍历,特殊情况下效率不高。

c\c++使用"\0"作为结束标志,ascii使用一个字节"\0",unicode使用两个字节"\0"。(注意程序会使用一个变量来存储字符串第一个字符的位置,以便于查找字符串)。

布尔类型

用于判断执行类型,分为0和非0.0为假,非0为真。

地址、指针和引用

c\c++中地址都以16进制表示,取一个变量的地址使用"&"符号,只有变量才能取地址。常量没有地址(不包括const定义的伪常量(其实是常量,但是设置为不可修改))。

指针定义使用"TYPE*"格式,type为数据类型,任何数据都可以定义为指针,指针本身也是一种数据类型,也用于保存各种数据类型在内存中的地址。指着变量也可以取出地址,所以有多级指针。

指针和地址的区别

不同点

|----------------|-------------|
| 指针 | 地址 |
| 变量,保存变量地址 | 常量,内存标号 |
| 可修改,再次保存其他变量地址 | 不可修改 |
| 可以对其执行取地址操作 | 不可执行取地址操作 |
| 包含对保存地址的解释信息 | 仅有地址值无法解释数据 |

共同点

|-------------|-------------|
| 指针 | 地址 |
| 取出指向地址内存的数据 | 取出地址对应内存的数据 |
| 对地址偏移后,取出数据 | 偏移后取数据,自身不变 |
| 求两个地址值的差 | 求两个地址的差 |

代码

不同指针访问同一个地址

代码

#include <stdio.h>

int main(){
int n = 0x12345678;
int *p1 = &n;
char *p2 = (char*)&n;
short *p3 = (short *)&n;
printf("%08x", *p1);
printf("%08x", *p2);
printf("%08x", *p3);
return 0;
}

反汇编

C

Wingw 64

main:

push %rbp

mov %rsp,%rbp

sub $0x40,%rsp

call 0x7ff6f3591877 ; <__main> movl $0x12345678,-0x1c(%rbp) ;n = 0x12345678

lea -0x1c(%rbp),%rax

mov %rax,-0x8(%rbp) ;int *p1=&n

lea -0x1c(%rbp),%rax

mov %rax,-0x10(%rbp) ;char *p2=&n

lea -0x1c(%rbp),%rax

mov %rax,-0x18(%rbp) ;short *p3 = &n

mov -0x8(%rbp),%rax ;*p1

mov (%rax),%eax ;读取p1

mov %eax,%edx ;参数2 p1

lea 0x8894(%rip),%rax ; 0x7ff6f359a000 参数1 %08 mov %rax,%rcx

call 0x7ff6f35916e0 ; <printf> mov -0x10(%rbp),%rax ;*p2

movzbl (%rax),%eax ;读取一个字节

movsbl %al,%eax ;扩展到32位

mov %eax,%edx

lea 0x8879(%rip),%rax ; 0x7ff6f359a000 参数1 %08x mov %rax,%rcx

call 0x7ff6f35916e0 ; <printf> mov -0x18(%rbp),%rax ;*p3

movzwl (%rax),%eax ;读取两个字节

cwtl ;ax扩展到eax

mov %eax,%edx

lea 0x8860(%rip),%rax ; 0x7ff6f359a000 参数1 %08x mov %rax,%rcx

call 0x7ff6f35916e0 ; <printf> mov $0x0,%eax

add $0x40,%rsp

pop %rbp

ret

c

msvc64->64

main(...):

pushq %rdi

subq $0x50, %rsp

leaq 0x20(%rsp), %rdi

movl $0xc, %ecx

movl $0xcccccccc, %eax ; imm = 0xCCCCCCCC 重置 rep stosl %eax, %es:(%rdi)

movl $0x12345678, 0x24(%rsp) ; imm = 0x12345678 leaq 0x24(%rsp), %rax

movq %rax, 0x38(%rsp) ;int *p1 = &n

leaq 0x24(%rsp), %rax

movq %rax, 0x40(%rsp) ;char *p2 = &n

leaq 0x24(%rsp), %rax

movq %rax, 0x48(%rsp) ;short *p3 = &n

movq 0x38(%rsp), %rax ;*p1

movl (%rax), %edx ;参数2 p1

leaq 0xaaf5(%rip), %rcx ;参数1 %08x

callq 0x140001195 ; printf movq 0x40(%rsp), %rax ;*p2

movsbl (%rax), %eax ;读取一个字节扩展到32位

movl %eax, %edx ;参数2 p2

leaq 0xaae7(%rip), %rcx ; 参数1 %08x

callq 0x140001195 ; printf movq 0x48(%rsp), %rax ;*p3

movswl (%rax), %eax ;读取两个字节扩展为32位

movl %eax, %edx ;参数2 p3

leaq 0xaad9(%rip), %rcx ;参数1 %08x

callq 0x140001195 ; printf xorl %eax, %eax

movl %eax, %edi

movq %rsp, %rcx

leaq 0x8736(%rip), %rdx

callq 0x140001046 ; _RTC_CheckStackVars movl %edi, %eax

addq $0x50, %rsp

popq %rdi

retq

msvc

x64->x86

main(...):

pushl %ebp

movl %esp, %ebp

subl $0x18, %esp

movl $0xcccccccc, %eax ; imm = 0xCCCCCCCC movl %eax, -0x18(%ebp)

movl %eax, -0x14(%ebp)

movl %eax, -0x10(%ebp)

movl %eax, -0xc(%ebp)

movl %eax, -0x8(%ebp)

movl %eax, -0x4(%ebp)

movl $0x12345678, -0x8(%ebp) ; imm = 0x12345678 leal -0x8(%ebp), %eax

movl %eax, -0x10(%ebp) ;int *p1 = &n

leal -0x8(%ebp), %ecx

movl %ecx, -0x14(%ebp) ;char *p2 = &n

leal -0x8(%ebp), %edx

movl %edx, -0x18(%ebp) ;short *p3 = &n

movl -0x10(%ebp), %eax ;*p1

movl (%eax), %ecx

pushl %ecx ;参数2 *p1

pushl 0x919000 *; imm = 0x919000* *(参数1 %08x)* calll 0x40119a *; _printf* addl 0x8, %esp ;平衡栈

movl -0x14(%ebp), %edx ;*p2

movsbl (%edx), %eax ;取出1字节扩展为32位

pushl %eax ;参数2 *p2

pushl 0x919008 *; imm = 0x919008* *(参数1 %08x)* calll 0x40119a *; _printf* addl 0x8, %esp ;平衡栈

movl -0x18(%ebp), %ecx ;*p3

movswl (%ecx), %edx ;取出2字节扩展为32位

pushl %edx ;参数2 *p3

pushl 0x919010 *; imm = 0x919010* *(参数1 %08x)* calll 0x40119a *; _printf* addl 0x8, %esp

xorl %eax, %eax

pushl %edx

movl %ebp, %ecx

pushl %eax

leal 0x911554, %edx

calll 0x401050 ; @_RTC_CheckStackVars@8 popl %eax

popl %edx

addl $0x18, %esp

cmpl %esp, %ebp

calll 0x4010ff ; __RTC_CheckEsp movl %ebp, %esp

popl %ebp

retl

nop

addl %eax, (%eax)

addb %al, (%eax)

popl %esp

adcl $0xfff80091, %eax ; imm = 0xFFF80091

各类型指针的寻址方式

代码

#include <stdio.h>

int main(int argc, char* argv\[\]){
char ary5 = {(char)0x01, (char)0x23, (char)0x45, (char)0x67, (char)0x89};
int *p1 = (int*)ary;
char *p2 = (char*)ary;
short *p3 = (short*)ary;

p1 += 1;
p2 += 1;
p3 += 1;
return 0;
}

反汇编

C

Wingw 64

main(int, char**):

push %rbp

mov %rsp,%rbp

sub $0x40,%rsp

mov %ecx,0x10(%rbp)

mov %rdx,0x18(%rbp)

call 0x7ff74fc41817 ; <__main> movl $0x67452301,-0x1d(%rbp)

movb $0x89,-0x19(%rbp) ;char ary5

lea -0x1d(%rbp),%rax

mov %rax,-0x8(%rbp) ;int *p1

lea -0x1d(%rbp),%rax

mov %rax,-0x10(%rbp) ;char *p2

lea -0x1d(%rbp),%rax

mov %rax,-0x18(%rbp) ;chort *p3

addq $0x4,-0x8(%rbp) ;p1 += 1

addq $0x1,-0x10(%rbp) ;p2 += 1

addq $0x2,-0x18(%rbp) ;p3 += 1

mov $0x0,%eax

add $0x40,%rsp

pop %rbp

ret

c

msvc64->64

main(int,char **):

movq %rdx, 0x10(%rsp)

movl %ecx, 0x8(%rsp)

pushq %rdi

subq $0x60, %rsp

leaq 0x20(%rsp), %rdi

movl $0x10, %ecx

movl $0xcccccccc, %eax ; imm = 0xCCCCCCCC rep stosl %eax, %es:(%rdi)

movl 0x70(%rsp), %ecx

movq 0xab26(%rip), %rax ; __security_cookie canary xorq %rsp, %rax

movq %rax, 0x58(%rsp) ;cookie ^ rsp 的结果保存在栈上

movb $0x1, 0x24(%rsp)

movb $0x23, 0x25(%rsp)

movb $0x45, 0x26(%rsp)

movb $0x67, 0x27(%rsp)

movb $-0x77, 0x28(%rsp) ;写入char ary5的数据

leaq 0x24(%rsp), %rax

movq %rax, 0x38(%rsp) ;int *p1

leaq 0x24(%rsp), %rax

movq %rax, 0x40(%rsp) ;char *p2

leaq 0x24(%rsp), %rax

movq %rax, 0x48(%rsp) ;short *p3

movq 0x38(%rsp), %rax

addq $0x4, %rax

movq %rax, 0x38(%rsp) ;*p1 += 1

movq 0x40(%rsp), %rax

incq %rax

movq %rax, 0x40(%rsp) ;*p2 += 1

movq 0x48(%rsp), %rax

addq $0x2, %rax

movq %rax, 0x48(%rsp) ;*p3 += 1

xorl %eax, %eax

movl %eax, %edi

movq %rsp, %rcx

leaq 0x86b0(%rip), %rdx

callq 0x140001046 ; _RTC_CheckStackVars 运行时栈检查 movl %edi, %eax

movq 0x58(%rsp), %rcx

xorq %rsp, %rcx

callq 0x140001055 ; __security_check_cookie cannry addq $0x60, %rsp

popq %rdi

retq

msvc

x64->x86

main(int,char **):

pushl %ebp

movl %esp, %ebp

subl $0x20, %esp

movl $0xcccccccc, %eax ; imm = 0xCCCCCCCC movl %eax, -0x20(%ebp)

movl %eax, -0x1c(%ebp)

movl %eax, -0x18(%ebp)

movl %eax, -0x14(%ebp)

movl %eax, -0x10(%ebp)

movl %eax, -0xc(%ebp)

movl %eax, -0x8(%ebp)

movl %eax, -0x4(%ebp)

movl 0x949000, %eax

xorl %ebp, %eax

movl %eax, -0x4(%ebp)

movb $0x1, -0x10(%ebp)

movb $0x23, -0xf(%ebp)

movb $0x45, -0xe(%ebp)

movb $0x67, -0xd(%ebp)

movb $-0x77, -0xc(%ebp) ;char ary5

leal -0x10(%ebp), %eax

movl %eax, -0x18(%ebp) ;int *p1

leal -0x10(%ebp), %ecx

movl %ecx, -0x1c(%ebp) ;char *p2

leal -0x10(%ebp), %edx

movl %edx, -0x20(%ebp) ;chort *p3

movl -0x18(%ebp), %eax

addl $0x4, %eax

movl %eax, -0x18(%ebp) ;p1 += 1

movl -0x1c(%ebp), %ecx

addl $0x1, %ecx

movl %ecx, -0x1c(%ebp) ;p2 += 1

movl -0x20(%ebp), %edx

addl $0x2, %edx

movl %edx, -0x20(%ebp) ;p3 += 1

xorl %eax, %eax

pushl %edx

movl %ebp, %ecx

pushl %eax

leal 0x941540, %edx

calll 0x401050 ; @_RTC_CheckStackVars@8 运行时栈检查 popl %eax

popl %edx

movl -0x4(%ebp), %ecx

xorl %ebp, %ecx

calll 0x401217 ; @__security_check_cookie@4 canary movl %ebp, %esp

popl %ebp

retl

nop

addl %eax, (%eax)

addb %al, (%eax)

decl %eax

adcl $0xfff00094, %eax ; imm = 0xFFF00094

以指针保存的地址作为寻址首地址,加上偏移量,最终得到目标地址。地址寻址公式如下:

type *p; //type指任意类型指针

p + n的目标地址 = 首地址 + sizeof(指针类型 type) * n

注意两个指针的值可以相加,但是没有意义(两个地址值相加会得到一个新地址,这个地址不可控往往无法访问所以无意义,不是不能相加)

减法对指针是有意义的,乘法和除法是没有意义的(原因和上面相同),减法操作必须是同类型指针(强转也可以),一般用于两个指针的大小比较,也可以用于计算数组元素个数:

type *p;//type指任意类型指针

p -- q = ((int)p -- (int)q) / sizeof(指针类型 type)

引用

C++为了简化操作,对指针操作进行了封装,产生了应用类型。

代码

#include <stdio.h>

void add(int &ref){
ref++;
}

int main(int argc, char argv\[\]){
int n = 0x12345678;
int &ref = n;
add(ref);
return 0;
}

汇编

C

Wingw 64

main(int, char*):

push %rbp

mov %rsp,%rbp

sub $0x30,%rsp

mov %ecx,0x10(%rbp)

mov %rdx,0x18(%rbp)

call 0x7ff75ebf1817 ; <__main> movl $0x12345678,-0xc(%rbp) ;int n = 0x12345678

lea -0xc(%rbp),%rax

mov %rax,-0x8(%rbp) ;int &ref = n

mov -0x8(%rbp),%rax ;参数1

mov %rax,%rcx ;传递n的地址

call 0x7ff75ebf16e0 ; <add(int&)> mov $0x0,%eax

add $0x30,%rsp ;平衡栈

pop %rbp

ret

c

msvc64->64

main(int,char *):

movq %rdx, 0x10(%rsp)

movl %ecx, 0x8(%rsp)

pushq %rdi

subq $0x40, %rsp

leaq 0x20(%rsp), %rdi

movl $0x8, %ecx

movl $0xcccccccc, %eax ; imm = 0xCCCCCCCC rep stosl %eax, %es:(%rdi)

movl 0x50(%rsp), %ecx

movl $0x12345678, 0x24(%rsp) ; int n= 0x12345678 leaq 0x24(%rsp), %rax

movq %rax, 0x38(%rsp)

movq 0x38(%rsp), %rcx ;int &ref = n

callq 0x140001244 ; add(int & __ptr64) xorl %eax, %eax

movl %eax, %edi

movq %rsp, %rcx

leaq 0x86d3(%rip), %rdx

callq 0x140001046 ; _RTC_CheckStackVars movl %edi, %eax

addq $0x40, %rsp

popq %rdi

retq

msvc

x64->x86

main(int,char *):

pushl %ebp

movl %esp, %ebp

subl $0x10, %esp

movl $0xcccccccc, %eax ; imm = 0xCCCCCCCC movl %eax, -0x10(%ebp)

movl %eax, -0xc(%ebp)

movl %eax, -0x8(%ebp)

movl %eax, -0x4(%ebp)

movl $0x12345678, -0x8(%ebp) ; int n = 0x12345678 leal -0x8(%ebp), %eax

movl %eax, -0x10(%ebp) ;int &ref = n

movl -0x10(%ebp), %ecx

pushl %ecx ;参数1传递n的地址

calll 0x4010eb ; add(int &) addl $0x4, %esp ;平衡栈

xorl %eax, %eax

pushl %edx

movl %ebp, %ecx

pushl %eax

leal 0x311534, %edx

calll 0x401050 ; @_RTC_CheckStackVars@8 popl %eax

popl %edx

addl $0x10, %esp

cmpl %esp, %ebp

calll 0x401104 ; __RTC_CheckEsp __RTC_CheckEsp 是另一个 MSVC Debug 独有的检查函数,用于验证栈平衡(esp == ebp)。如果栈指针被意外修改(如错误调用约定导致栈不平衡),该函数会触发断点或报错。 movl %ebp, %esp

popl %ebp

retl

nopl (%eax)

addl %eax, (%eax)

addb %al, (%eax)

cmpb $0x15, %al

xorl %eax, (%eax)

clc

除了引用是编译器实现寻址,而指针需要手动寻址外,引用和指针没有太大区别

引用类型作为函数参数

代码

void add(int &ref){
ref++;
}

汇编

C

Wingw 64

add(int&):

push %rbp

mov %rsp,%rbp

mov %rcx,0x10(%rbp) ;保护引用指针

mov 0x10(%rbp),%rax

mov (%rax),%eax ;取出参数ref的内容放入eax

lea 0x1(%rax),%edx ;+1

mov 0x10(%rbp),%rax

mov %edx,(%rax) ;写回去

nop

pop %rbp

ret

c

msvc64->64

add(int &):

movq %rcx, 0x8(%rsp) ;保护指针

pushq %rdi

movq 0x10(%rsp), %rax ;取出参数ref放入rax

movl (%rax), %eax

incl %eax ;+1

movq 0x10(%rsp), %rcx

movl %eax, (%rcx) ;写回去

popq %rdi

retq ;0xcc debug 对齐用

int3

int3

int3

int3

int3

int3

int3

int3

msvc

x64->x86

add(int &):

pushl %ebp

movl %esp, %ebp ;栈帧

movl 0x8(%ebp), %eax

movl (%eax), %ecx ;取出ref放入eax

addl $0x1, %ecx ;+1

movl 0x8(%ebp), %edx

movl %ecx, (%edx) ;写回去

popl %ebp

retl

int3

int3

int3

int3

int3

int3

int3

int3

int3

int3

int3

int3

int3

int3

相关推荐
182******20832 小时前
2026年学C语言还有出路吗?学习需要报班吗?
c语言·开发语言·学习
luj_17683 小时前
局部两极分析破解数学建模难题
服务器·c语言·开发语言·经验分享·算法
bubiyoushang8884 小时前
基于 C/C++ 的 MQTT 物联网通信协议实现
c语言·c++·物联网
三品吉他手会点灯5 小时前
C语言学习笔记 - 46.运算符和表达式 - 运算符4 - 对初学运算符的一些建议
c语言·开发语言·笔记·学习
小四季豆6 小时前
《数据结构与算法》-顺序表:算法落地的第一个线性结构
c语言·数据结构·算法
jimy17 小时前
C语言中使用“结构体 + 函数指针”来模拟面向对象编程(OOP
c语言
三品吉他手会点灯7 小时前
C语言学习笔记 - 45.运算符和表达式 - 运算符3 - 逻辑运算符
c语言·笔记·学习
玖玥拾7 小时前
C/C++ 基础笔记(五)
c语言·c++·指针
QT-Neal8 小时前
C/C++ 程序段的概念与分类
c语言·c++