zoukankan      html  css  js  c++  java
  • 关于字符串的内存分配问题

            字符串可以分配在栈,堆,和字符串常量区(静态数据区)中,这三种不同的内存结构具有不同的页(page)属性,这些属性决定了三种不同的字符串特性:

    1.在栈上分配字符串

        在栈上分配字符串实质上又可以分成两种方法,具体方法如下所示:

        ① TCHAR str1[5] = {'H', 'e', 'l', 'l', 'o'};

    如图上图所示,它是语句①汇编后的代码,图中的红色方框一中的代码表示在栈上分配16个字节,红色方框二中的代码,将字符'H', 'e', 'l', 'l', 'o'的ASCII码分别保存到栈中分配的内存中。显然在这种方式分配的内存中保存字符串时,我们必须要显示地为''分配空间并赋值,只有这样我们才可以得到C-Style(以0结尾)的字符串。例如: TCHAR str1[6] = {'H', 'e', 'l', 'l', 'o', ''}

    这时的str1才能够作为字符串参数,传递给需要字符串参数的函数(printf, strcpy, strcmp等等)。str1的意义是指向栈中字符数组的首地址,即str1 = [ebp + var_10],显然str1表示栈中某个字节的地址,所以他不能被重定向,也就是不重新被赋值,例如:语句str1 = TEXT("hello");就是错误的。但str1所指向的栈空间中的字符数据是可以被重新赋值的,例如str1[0]='h'这条语句就是合法的。

    ② TCHAR str2[6] = TEXT("Hello");

    进行反编译后的代码如下图所示:

    其中的ds:dword_406110,ds:dword_406114,ds:dword_406118如下图所示:

    显然TEXT("Hello")首先被存放在数据段的只读内存区域(字符串常量区)中,其赋值过程是:首先分配栈空间,然后将存放在只读内存区域的字符串复制到栈空间中,str2仍然是栈分配的保存字符串的首地址。这个地址是const型的量,所以他不能被重新赋值,也就是说:语句str2 = TEXT("World")是错误的。但str2所指向的栈空间中的字符数据是可以被重新赋值的,例如str2[0]='h'这条语句就是合法的。

    2.在字符串常量区(静态数据区)分配字符串

    方法:LPTSTR str3 = TEXT("Hello, world.");

    字符串字面量TEXT("Hello, world.")存放在PE文件的静态数据区中,该数据段中数据有两个特性:IMAGE_SCN_CNT_INITIALIZED_DATA 和 IMAGE_SCN_MEM_READ 这两个特性分别表示:该数据段中的数据是被初始化过的(The section contains initialized data.),该数据段中的数据时可读的(The section can be read)
    也就是说静态数据区中的数据是不可写的,所以我们无法改变str3所指向的数据,因此代码str3[0] = 'h'是不合法的,它无法执行。但是变量str3却是分配在栈中的变量,所以str3是可以被重新赋值的,例如str3 = TEXT("Welcome");是合法的。
    3.字符串在堆中分配
     ① 要在堆中使用字符串,首先要分配足够的内存,分配内存的原则是,分配的大小至少是(假设要存放count个字符,其中不包括字符'')bytes = (count + 1) * sizeof(TCHAR),一定要为NULL分配空间。当然,也可以分配大于bytes的空间。常用的分配内存的函数malloc,常用的求字符串长度(字符串中字符的个数)的函数_tcslen
     ② 堆中申请的内存,常常含有其他垃圾数据,在使用之前最好先进行清零操作,使用函数memset/wmemset,ZeroMemory等可以完成该要求。
     ③ 赋值使用的函数有strcpy/wstrcpy,strcpy_s/wstrcpy_s等
     ④ 使用完后,释放发分配的内存,free函数。
    举例:

    LPTSTR str = TEXT("hello, world");
    int bytes = sizeof(TCHAR)*(_tcslen(str)+1);
    LPTSTR buffer = reinterpret_cast<LPTSTR>(malloc(bytes));
    ZeroMemory(buffer, bytes);
    _tcscpy(buffer, str);
    //buffer = str;
    _tprintf_s(TEXT("buffer: %X	%s
    "), buffer, buffer);
    _tprintf_s(TEXT("str: %X	%s
    "), str, str);
    free(buffer);


    注意被注释的红色代码,这是有时候最容易犯的错误,明明分配了内存却又直接通过 = 赋值,这实际上是将字符串字面量TEXT("hello, world")的地址直接赋给了栈变量buffer。对与str它自身既可以被重新赋值(注意对str重新赋值时,要使用free函数释放str原来指向的内存,否则会造成内存泄露),同时它所指向的内存区域也可以被重新赋值.

    注:当使用CreateProcess函数来创建一个新的进程时
    BOOL WINAPI CreateProcess(
      __in_opt     LPCTSTR lpApplicationName,
      __inout_opt  LPTSTR lpCommandLine,
      __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
      __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
      __in         BOOL bInheritHandles,
      __in         DWORD dwCreationFlags,
      __in_opt     LPVOID lpEnvironment,
      __in_opt     LPCTSTR lpCurrentDirectory,
      __in         LPSTARTUPINFO lpStartupInfo,
      __out        LPPROCESS_INFORMATION lpProcessInformation
    );
    注意该函数的第二个参数,当使用CreateProcessW函数时,该函数内部会修改参数lpCommandLine,所以这个参数所指向的内存不能是只读的内存,例如const变量,或者字符串字面量。
    The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

  • 相关阅读:
    自定义序列化类注意事项
    HashMap中的modCount
    transient
    30张图带你彻底理解红黑树(转)
    hashcode和hash算法
    线程池
    VS2015 NuGet错误:远程证书无效
    解决前台JS弹框Alert点击确定页面会刷新
    Regex中Replace方法的简单实用
    .ashx 一般处理程序
  • 原文地址:https://www.cnblogs.com/a-ray-of-sunshine/p/3416061.html
Copyright © 2011-2022 走看看