【问题标题】:Need suggestion for manipulating memory effectively in C++?需要在 C++ 中有效地操作内存的建议吗?
【发布时间】:2011-04-19 00:44:15
【问题描述】:

我编写了一个小程序来查找既是质数又是特定数 n 的因数的数。我从一个文件中获取数字 n 并将其打印到另一个文件中。

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>

using namespace std;
bool PrimeFactor(int );
int countSize(int);


char * p_str = "";//store result of prime factor
int size = 0;//length of p_str

int main(int argc, char** argv) {
std::ifstream fin;
fin.open("input.txt");

std::ofstream fout;
fout.open("output.txt", std::ios::app);

int N;//number to factor

while (fin >> N){   
    //Find prime factor
    PrimeFactor(N);
    fout << p_str;// print string storing result
    fout << endl;
}

fin.close();
fout.close();

return 0;
}

bool PrimeFactor( int n ){
int count = 0;// count divisors

for (int i = 2 ; i < n; i++){
    if ( n % i == 0 ){
        // convert integer to string
        char * tmpstr = new char[ countSize(i) ]  ;// allocate according to size of integer (not waste memory)
        sprintf( tmpstr, "%d ", i); // NOTE : if not have the blank after %d -> no space

        // condition : prime and not duplicate existing number in global string
        if ( PrimeFactor(i) && !strstr( p_str, tmpstr ) ){

            char * new_p = new char [ size + countSize(i) ];
            strcpy( new_p, p_str );// copy the global string to new place
            strcat( new_p, tmpstr );// append new integer value
            p_str = new_p ; // let glocal string be renewed with appending new result
            size = size + countSize(i);
            cout << size << endl;
        }
        count ++;
    }
}

if (count > 0)//not prime
    return false;
return true;
}

//counting the number of digit of an integer
int countSize(int n){
    int count = 0;
    if (n < 10)
        return 1;
    while ( n >= 10 ){
        count++;
        n = n/10;
    }
    return count + 1;
}

使用这种方法,如果我不将创建的数字存储在 C 字符串中并检查下一个数字是否已经是字符串中的数字,则结果可能会重复。我选择 C-string 因为它比 std::string 更具挑战性。 所以问题是关于操纵字符串以最小化内存使用。我必须使用指向字符的全局指针(因为不必定义字符数组的大小)。 似乎 func CountSize() 虽然返回了所需的内容(字符串中数字的长度),但字符串仍然浪费了一些内存,并且 size 变量不是我的意思。此外,我无法通过使用 sizeof() 和指向字符的指针来获取大小。 任何人都可以帮助我吗?

【问题讨论】:

  • 我会说浪费内存是您最不担心的事情。我会使用删除来查看内存管理。您必须计算字符数,并确保记住最后为 null 的 +1。
  • 我会使用 std::string 并专注于查找您正在寻找的数字。为什么要最小化小字符串的内存使用量?为什么你认为你比标准库做得更好?
  • 一个 char* 字符串而不是一个 std::string 来存储你的数字列表? std::vector 怎么样?
  • 我曾考虑过 std::string 和 vector,但让我们假设我们正在处理 C,而不是 C++。我想把自己放在一个 C 程序员的位置上考虑一下。
  • 然后把你问题上的标签改成C。

标签: c++ memory-management


【解决方案1】:

好的,所以你想使用 char* 字符串,大概是为了将来处理它们。这是令人钦佩的。但是你首先需要一个关于 c-string 管理诫命的速成课程......

您应将每个 newdelete 配对

您的目标是尽量减少内存使用量,对吧?好吧,每次你用new 创建一个字符串,但不要删除它,你就是在泄漏内存。这通常是一种不好的做法,但也是一种明确的内存浪费。在您的情况下,您使用new [] 进行分配以创建一个数组,并且new [] 调用必须与delete [] 配对。所以在你的 PrimeFactor 函数中:

strcpy( new_p, p_str );// copy the global string to new place
strcat( new_p, tmpstr );// append new integer value
// delete original contents of p_str
delete [] p_str;
p_str = new_p ; // let glocal string be renewed with appending new result

您还需要在程序的最后加上delete [],以便在 p_str 退出之前清理它的内存。

你应该总是为空终止符腾出空间

当计算机在读取一个 c 字符串时,它事先并不知道它有多长。如果你有一个用内容“Hi”初始化的 char[100],计算机如何知道在 'i' 之后停止,而不是 'H' 或 'i' 之后的字符 5? C 字符串是无效的,除非它们以空终止符结尾,写为 '\0'。空终止符向计算机指示:“好的,我们完成了。”字符串结束了。这有点漂亮,但可能会出现问题,因为空终止符在字符数组中占据了一个位置。存储“Hi”需要 char [3]; -- char[0] 是 'H',char[1] 是 'i',char[2] 是 '\0'。所以你的新字符串分配代码应该是这样的:

    char * tmpstr = new char[ countSize(i) + 1 ]  ;// allocate according to size of integer (not waste memory)
    ...
        char * new_p = new char [ size + countSize(i) + 1 ];

注意+ 1s。这是为了确保您的字符串为 Null 终止符留出空间。

你应该使用字符串安全的函数

sprintfstrcpystrcat(和其他)已被弃用,取而代之的是新的 sprintf_sstrcpy_sstrcat_s 函数。 _s 代表“安全”。这些函数需要一个额外的参数来表示它们正在修改的字符串的大小,并确保不会破坏大小限制。所有字符串修饰符函数确保前面提到的空终止符被附加,但是在您的代码中,您没有给他们适当的空间来做到这一点。因此,非字符串安全函数将您声明的内存过去的一个字符写入未知内存 - 糟糕 - 非常糟糕。相反,这些函数的字符串安全版本会使您的程序因错误而崩溃,提醒您有问题需要修复。函数的字符串安全实现如下所示:

    int tmpstrSize = countSize( i ); // calculate tmpstrSize once
    char * tmpstr = new char[ tmpstrSize + 1 ]  ;// allocate according to size of integer (not waste memory)
    sprintf_s( tmpstr, tmpstrSize + 1, "%d ", i); // NOTE : if not have the blank after %d -> no space
    ...
        int new_pSize = size + tmpstrSize; // calculate new_pSize once
        char * new_p = new char [ new_pSize + 1 ];
        strcpy_s( new_p, new_pSize, p_str );// copy the global string to new place
        strcat_s( new_p, new_pSize, tmpstr );// append new integer value

现在你很好,很安全,如果出现问题,你会知道

你应该以 C++ 的方式编写 C++ 代码

说实话,您上面编写的程序并不是真正的 C++,而是 C。当然,它可以在 C++ 环境中正常运行,但该方法完全基于 C。 C++ 程序对字符串使用std::strings,对整数列表使用std::vectors。所以嘿,我知道你想为挑战学习低级的东西,我自己也去过那里。但是一旦你知道如何去做,基本上我上面描述的所有内容都与字符串处理无关的 C++ 功能是一个天赐之物,你永远不会想要回去。

作为一个小旁注,我建议查看Sieve of Eratosthenes 以检查素数。这是一个很好的练习,可以极大地提升您的代码的性能。

【讨论】:

  • 请注意sprintf_s 和朋友是非标准的微软扩展。标准版本称为snprintfstrncpystrncat。 (学究式地,snprintf 也不是标准 C++ 函数,但其​​他函数在 C 和 C++ 中都是标准的)。
【解决方案2】:

对于您正在尝试做的事情,字符串并不是真正合适的数据结构。您似乎担心内存消耗,但为什么呢?您的程序实际上是否内存不足?使用字符串来完成这项任务会带来很多不必要的工作:分配内存、复制字符串以追加新数字、在字符串中搜索现有数字、将整数转换为字符串、计算整数中的位数超过必要的次数, 等等。使用 C 字符串也很容易引入 bug:非空终止的字符串、缓冲区溢出等。例如,当您将整数转换为字符串时,您不会为空终止符分配一个字节,所以 @987654321 @ 溢出你的缓冲区。

更合适的数据结构是一组整数。一个集合只能存储一次值。您可以使用find 方法查看集合中是否已存在项目。使用一个集合可能会使用更多的内存,但是你的程序会非常非常快,因为你会用 O(1) 和 O(log n) 操作替换很多 O(N) 操作。

您不能使用sizeof 来获取已分配数组的大小。那是因为sizeof 返回类型的大小,所以当你在 C 字符串上使用 sizeof 时,你得到的是指针的大小,而不是数组的大小。您必须自己跟踪数组的大小。

您提到使用 C 字符串而不是 std::string 因为它更具挑战性。我赞扬您尝试具有挑战性的事情,因为这是扩展您的极限并学习新事物的好方法。如果我可以提出一个建议:先做可能可行的最简单的事情。编写测试以确保它执行您认为它正在执行的操作。有了一个工作程序和一个验证测试,您就可以开始优化内存消耗、性能或具有挑战性的数据结构以获得乐趣。该测试允许您验证您的优化在优化时没有引入错误。

【讨论】:

    【解决方案3】:

    将整数存储为字符串,您是在询问有效的内存管理吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      • 2012-01-28
      • 1970-01-01
      • 2014-12-12
      • 1970-01-01
      相关资源
      最近更新 更多