string的简单使用
#include <iostream>
#include <string>
using std::string;
using std::cout; using std::endl;
int main(void)
{
string s = "abcd";
for (auto &c : s)
c = toupper(c);
cout << s << endl;
return 0;
}
来看第一句
string s = 'abcd';
mscv2015反汇编 32位
.text:0040102E lea ecx, [ebp+Memory] ; Dst
.text:00401031 mov [ebp+var_18], 0
.text:00401038 mov [ebp+var_14], 0Fh
.text:0040103F mov byte ptr [ebp+Memory], 0
.text:00401043 call sub_401180
clang+llvm反汇编 64位
.text:0000000000400CE2 lea rax, [rbp+var_30]
.text:0000000000400CE6 mov rdi, rax
.text:0000000000400CE9 mov [rbp+var_68], rax
.text:0000000000400CED call __ZNSaIcEC1Ev ; std::allocator<char>::allocator(void)
.text:0000000000400CF2 mov ecx, offset aAbcd ; "abcd"
.text:0000000000400CF7 mov esi, ecx
.text:0000000000400CF9 lea rdi, [rbp+var_28]
.text:0000000000400CFD mov rdx, [rbp+var_68]
.text:0000000000400D01 call __ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EPKcRKS3_ ; std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(char const*,std::allocator<char> const&)
.text:0000000000400D06 jmp $+5
.text:0000000000400D0B
.text:0000000000400D0B loc_400D0B:
.text:0000000000400D0B lea rdi, [rbp+var_30]
.text:0000000000400D0F call __ZNSaIcED1Ev ; std::allocator<char>::~allocator()
可以看到,mscv编译器中,主要调用了sub_401180函数,C++在定义变量时有直接初始化和拷贝初始化两种方式,这里使用的是拷贝初始化,所以这个函数应该是拷贝构造函数。函数内部比较复杂。。不过可以明显看到几处关键部分
.text:0040124C push edi
.text:0040124D push offset aAbcd ; "abcd"
.text:00401252 push eax ; Dst
.text:00401253 mov [ebp+var_4], eax
.text:00401256 mov [esi+10h], edi
.text:00401259 mov [esi+14h], ebx
.text:0040125C call memcpy
······
.text:004011A1 push edi
.text:004011A2 push offset aAbcd ; "abcd"
.text:004011A7 push ebx ; Dst
.text:004011A8 mov [esi+10h], edi
.text:004011AB call memmove
当使用直接初始化时,我们实际上时要求编译器选择普通的函数中与我们提供的参数最匹配的构造函数;当使用拷贝初始化时,我们要求编译器将运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。拷贝初始化通过拷贝构造函数和移动构造函数来完成。
拷贝初始化发生时机:
- 使用 = 定义变量时
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类的成员
拷贝构造函数的第一个参数必须为引用的原因:
当我们调用一个函数时,若函数参数并非引用类型,则会调用拷贝构造函数来传参,若拷贝构造函数的参数也不是引用,那么就会无限调用自身为自己传值。
而在clang+llvm中,则是直接使用了
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string
其声明如下
basic_string(
const basic_string& right,
size_type _Roff,
size_type count = npos);
所以本质上还是使用的拷贝构造函数
来看接下来一段代码
for (auto &c : s)
c = toupper(c);
mscv2015反汇编 32位
.text:00401055 lea esi, [ebp+input]
.text:00401058 cmp ecx, 10h
.text:0040105B cmovnb eax, [ebp+input]
.text:0040105F cmovnb esi, [ebp+input]
.text:00401063 xor edx, edx
.text:00401065 add eax, [ebp+var_18]
.text:00401068 xor edi, edi
.text:0040106A mov ebx, eax
.text:0040106C sub ebx, esi
.text:0040106E cmp esi, eax
.text:00401070 cmova ebx, edx
.text:00401073 test ebx, ebx
.text:00401075 jz short loc_40109A
.text:00401077 nop word ptr [eax+eax+00000000h]
.text:00401080
.text:00401080 loc_401080: ; CODE XREF: sub_401000+95↓j
.text:00401080 movsx eax, byte ptr [esi]
.text:00401083 push eax ; C
.text:00401084 call ds:toupper
.text:0040108A inc edi
.text:0040108B mov [esi], al
.text:0040108D add esp, 4
.text:00401090 lea esi, [esi+1]
.text:00401093 cmp edi, ebx
.text:00401095 jnz short loc_401080
clang+llvm反汇编 64位
.text:0000000000400D20 call __ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5beginEv ; std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(void)
.text:0000000000400D25 mov [rbp+var_50], rax
.text:0000000000400D29 mov rdi, [rbp+var_48]
.text:0000000000400D2D call __ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE3endEv ; std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(void)
.text:0000000000400D32 mov [rbp+var_58], rax
.text:0000000000400D36
.text:0000000000400D36 loc_400D36: ; CODE XREF: main+AE↓j
.text:0000000000400D36 lea rdi, [rbp+var_50]
.text:0000000000400D3A lea rsi, [rbp+var_58]
.text:0000000000400D3E call sub_400E10
.text:0000000000400D43 test al, 1
.text:0000000000400D45 jnz loc_400D50
.text:0000000000400D4B jmp loc_400D9A
.text:0000000000400D50 ; ---------------------------------------------------------------------------
.text:0000000000400D50
.text:0000000000400D50 loc_400D50: ; CODE XREF: main+75↑j
.text:0000000000400D50 lea rdi, [rbp+var_50]
.text:0000000000400D54 call sub_400E50
.text:0000000000400D59 mov [rbp+var_60], rax
.text:0000000000400D5D mov rax, [rbp+var_60]
.text:0000000000400D61 movsx edi, byte ptr [rax] ; c
.text:0000000000400D64 call _toupper
.text:0000000000400D69 mov cl, al
.text:0000000000400D6B mov rdx, [rbp+var_60]
.text:0000000000400D6F mov [rdx], cl
.text:0000000000400D71 lea rdi, [rbp+var_50]
.text:0000000000400D75 call sub_400E70
.text:0000000000400D7A mov [rbp+var_70], rax
.text:0000000000400D7E jmp loc_400D36
可以看到,mscv2015的编译结果在循环中完全是内存操作,引用和指针在汇编级别几乎没有区别;而clang+llvm则是使用了begin和end两个迭代器的东西来遍历字符串,虽然本质上它也只是返回内存地址。
关于这个循环结构可以看到,在mscv编译器中,for循环被优化成了if语句+do while循环的模式,而后者则看起来更像一个while循环。
到此结束了。