2016-03-08 175 views
2

該程序寫入SQLite數據庫,消息通過無線模塊接收。但不知何故,每次收到一條消息並將其寫入數據庫時​​,都會發生內存泄漏,因爲大約10 000次寫入程序使用的是1GB內存。使用sqlite3與C++時的內存泄露

SQLite3 with C++的文件說,內存泄漏阻止與sqlite3_finalize()sqlite3_close()其中存在:

#include <iostream> 
#include <string> 
#include <sstream> 
#include "sqlite3.h" 

using namespace std; 

#define DB "test.db" 
sqlite3 *dbfile; 

bool connectDB(); 
void disonnectDB(); 
int insOrUpdate(string s); 
int select(string s); 

struct messageStruct_t { 
    float value; 
}; 

bool isOpenDB = false; 

int main() { 
    int counter = 0; 
    while (1) { 
    int header = 1; 
    int message = rand() % 3; 

    if (message) { 
     counter ++; 

     switch (header) { 

     case 1: { 
     messageStruct_t recMessage; 
     recMessage.value = 55; 
     int receivedSendersID = 2; 

     //SQL query to get foreign key 
     stringstream strm_select; 
     strm_select << "SELECT id FROM table1 WHERE sendersID=" 
        << receivedSendersID; 
     string s_select = strm_select.str(); 
     cout << "SQL query: " << s_select << endl; 
     int sendersID = select(s_select); 
     cout << "Sender's ID: " << sendersID << endl; 


     if (sendersID == 0) { 
      cout << "Error: Sender doesn't exist\n"; 
     } else { 
      stringstream strm_insert; 
      strm_insert << "INSERT into table2(id,value,sender_id) values(" 
         << counter << ", " 
         << recMessage.value << ", " << sendersID << ")"; 
      string s_insert = strm_insert.str(); 
      cout << "SQL query: " << s_insert << endl; 
      insOrUpdate(s_insert); 
      cout << "Recorded data: " << recMessage.value << endl; 
     } 
     } 

     default: { 
     break; 
     } 

     } 
    } 
    } 
} 

bool connectDB() { 
    if (sqlite3_open(DB, &dbfile) == SQLITE_OK) { 
    isOpenDB = true; 
    return true; 
    } 
    return false; 
} 

void disonnectDB() { 
    if (isOpenDB == true) { 
    sqlite3_close(dbfile); 
    } 
} 

int insOrUpdate(string s) { 
    if (!connectDB()) { 
    return 0; 
    } 

    char *str = &s[0]; 
    sqlite3_stmt *statement; 
    int result; 
    const char *query = str; 

    if (sqlite3_prepare(dbfile, query, -1, &statement, 0) == SQLITE_OK) { 
    result = sqlite3_step(statement); 
    //the documentation says that this destroys the statement and prevents memory leaks 
    sqlite3_finalize(statement); 
    return result; 
    } 
    //and this destroys the db object and prevents memory leaks 
    disonnectDB(); 
    return 0; 
} 

int select(string s) { 
    if (!connectDB()) { 
    return 0; 
    } 

    char *str = &s[0]; 
    sqlite3_stmt *statement; 
    const char *query = str; 
    string returned; 
    if (sqlite3_prepare(dbfile, query, -1, &statement, 0) == SQLITE_OK) { 
    int ctotal = sqlite3_column_count(statement); 
    int res = 0; 

    while (1) { 
     res = sqlite3_step(statement); 
     if (res == SQLITE_ROW) { 
     for (int i = 0; i < ctotal; i++) { 
      string s = (char*)sqlite3_column_text(statement, i); 
      cout << s << " "; 
      returned = s; 
     } 
     cout << endl; 
     } 
     if (res == SQLITE_DONE || res == SQLITE_ERROR) { 
     cout << "done " << endl; 
     break; 
     } 
    } 
    } else { 
    cout << "Can't prepare" << endl; 
    return 0; 
    } 
    sqlite3_finalize(statement); 
    disonnectDB(); 

    int result; 
    stringstream convert(returned); 
    if (!(convert >> result)) { 
    result = 0; 
    } 

    return result; 
} 

CREATE TABLE table1 (
id INTEGER NOT NULL, 
sendersID INTEGER, 
PRIMARY KEY (id) 
); 
CREATE TABLE table2 (
id INTEGER NOT NULL, 
value FLOAT, 
sender_id INTEGER, 
FOREIGN KEY(sender_id) REFERENCES table1 (id) 
); 

INSERT INTO table1(sendersID) values(2); 
+1

請將樣本減少到可編譯運行的樣本。目前我需要發明'message','headerStruct','header'和'recMessage'甚至編譯它,希望實際的錯誤實際上是在你粘貼的代碼中,而不是在你選擇的某個部分中選擇的粘貼.... – Rumburak

+0

我修改了代碼進行編譯。我非常確定泄漏出現在那裏,因爲應用程序使用的內存在程序沒有收到消息時不會增長。 –

+0

@Rumburak,我添加了一個測試數據庫。內存泄露在這裏,4000次寫入後爲300MB。 –

回答

2

在你connectDB(..)調用,你不檢查數據庫在打開之前已經打開。你的內存泄漏可能來自這個數據庫重複映射到你的內存空間。

該程序可能存在其他問題,但對connectDB(..)的更改應該有助於在每次成功插入時發生泄漏。

bool connectDB() { 
    if (false == isOpenDB && sqlite3_open(DB, &dbfile) == SQLITE_OK) { 
     isOpenDB = true; 
    } 
    return isOpenDB; 
} 
+1

你打敗了我。只是一個評論:disconnectDb不重置isOpenDB,所以這實際上不會幫助。如果將dbfile初始化爲nullptr並在斷開連接中重置爲nullptr,則isOpenDB將不需要。那爲什麼我建議使用RAII。無論如何,它更乾淨,更容易:-) – Rumburak

+0

就是這樣,謝謝。 –

1

您絕對應該使用RAII來進行連接,也可以使用您的語句。有幾個地方可以提早返回而不清理聲明和/或關閉連接。

+0

謝謝你的建議,我會研究它。 –