将数据保存在.exe文件中

时间:2018-08-26 13:49:08

标签: c++11 exe

是否可以创建一个控制台应用程序来询问输入数据(例如,用户的生日,最喜欢的食物,一切),并且我对其进行编程以使其在关闭时将这些数据保存在该.exe中?

这样,当我再次打开它时,所有那些数据仍将保存在那里,因此我只需要更新或修改它们。

3 个答案:

答案 0 :(得分:4)

不要将数据保存在executable内内

  

是否可以创建一个程序来询问输入数据,然后在我关闭该文件时将这些数据保存在该.exe中?

没有简单的方法(但您不需要)。您想要的与persistenceapplication checkpointing有关。

实际上,您可能希望将数据存储在文件中,而不是可执行文件中(可能使用JSON之类的文本格式)或某种database(可能与某些{{3 }},或与sqlite之类的RDBMS进行交互)。对于诸如生日和饮食偏爱之类的事情,使用sqlite数据库文件可能是一种很好的方法(请参阅一些PostGreSQL)。为您的SQLite tutorial精心设计。

  

这样,当我再次打开它时,所有这些数据仍将保存在这里

如果将这些数据保存在某个外部文件中(也许是一个简单的myappdata.sqlite文件),这些数据仍然会存在。您可以轻松地设计程序来创建该文件(如果该文件不存在)(这种情况仅在您第一次运行程序时发生;在下次运行时,您的程序将在启动时从该外部文件中成功读取该数据)。

在最新的database schema(请阅读operating systems,以了解有关操作系统的更多信息)中,尤其是Windows,MacOSX,Linux,Android等...,this textbook应该是只读的。而且它可能同时在几个 executable中运行(在这种情况下,应该怎么办?考虑一下processes属性)。

通常的做法是将数据存储在可执行文件的外部 (大多数程序,包括文本处理器,编译器,网络浏览器…… )。您无需解释为什么要在可执行文件内 中存储一些数据,而这样做是不寻常的,并且是特定于操作系统和可执行文件格式的(对于Linux,请仔细研究ACID。 )

我建议将数据保存在某些可选文件(或数据库)中-它的文件路径可能具有一些有线常量默认值,等等。...在启动时,请检查是否存在该数据(例如在POSIX上使用elf(5),或仅通过处理fopensqlite3_open的故障情况等)。如果不存在,则以某种方式初始化程序数据。在退出时(或节省时间),您可以写入该数据。顺便说一句,大多数程序都在这样做。

请注意,在大多数操作系统和计算机上,软件不仅是单个可执行文件,而且还更多(例如,所需的库和依赖项,配置文件,数据文件,access(2)脚本,例如{{3} }等)。它的build automation是一个公认的技术过程(有时是一个非常复杂的过程),Makefile很有帮助。

我的感觉是,没有特定动机,您甚至不应该尝试(可变)数据(持久地)存储在可执行文件中(它非常复杂,易碎,因为它非常依赖于操作系统和编译器,并且特定于构建链,因此不寻常,并且会打开installation)。

为了完整起见,某些程序确实通过重写其可执行文件来写入一些数据。在Linux上,package managers在其vulnerabilities文件中(实际上非​​常脆弱,因为特定于OS和编译器)在实际中仅在安装过程中这样做,但是该功能是GNU emacs并且可能会消失。

许多其他系统巧妙地处理了正交持久性:unexec.c具有一些disputed原语(通常将状态保留在其他“图像”文件中)。 SBCL有一些save-lisp-and-die。 J.Pitrat的CAIA系统(请参见Poly/MLexport facility;可在this paper主页上获得2016 his blog(经许可))能够完全重新生成其所有C代码以及所有必需的数据(成千上万个文件)。 tarball of CAIA将其状态保存在组织良好的文件树中。这样的持久性或检查点技术与垃圾收集相关(因此您应该阅读my),并且正在使用接近复制垃圾收集的技术和算法。

FWIW,我当前的项目FullPliant正在正交保留其整个堆,但是在主可执行文件之外执行此操作(在理想的情况下,我想重新生成所有C或C ++源代码;我离目标还很远。

我的建议是将软件保留在几个文件中:可执行文件,与之相关的C ++源代码,数据文件(以及可能更多的依赖项,即共享库或DLL,字体和图像文件,所需的二进制文件等) ...)。这样,在保持状态时,您无需覆盖可执行文件。由于您提到的是C ++(不是 GC handbook),因此可以使用以下方法生成系统的C ++代码(称为bismon程序)它的持久数据(并将所有生成的C ++的重新编译留给系统的C ++编译器)。我还建议使您的自生成程序成为homoiconic。 (如果您这样做的话,很高兴将您的问题编辑为给出其URL)。

在C ++中,您可以按以下方式将数据保留在可执行文件中(同样,这是一个坏主意,我希望已经说服您避免这种方法): C或C ++源文件(例如mydata.cc)仅包含数据(例如大的const char data[]="... many lines of data ...";)-顺便说一句,Quine文件格式可能很受启发。您将所有其他*.o free software保留在程序已知的位置。要保存数据,请在每次保存操作中重新生成mydata.cc文件(使用当前状态的新数据),最后运行相应的命令(也许使用{{3} })编译该mydata.cc并将其与保留的*.o链接到一个新的可执行文件中。因此,每个保存操作都需要重新编译data.cc并将其与其他*.o对象文件链接(当然,C ++编译器和链接器,可能还有其他构建自动化工具,会成为程序的必需依赖项) 。这样的方法并不比保留外部数据文件简单(并且需要保留这些*.o对象文件)。

  

这样,当我再次打开它时,所有这些数据仍将保存在这里

如果您的目标只是获取过去写入的数据,只需将数据保存在某些可选数据库或文件中(就像许多程序一样:您的文字处理器会要求您如果在没有任何文档的情况下启动它,请在退出之前保存其文档,然后在可执行文件的 outside 中写下一些文字,然后在退出程序之前将其写入。无需覆盖您的可执行文件!

答案 1 :(得分:1)

要执行的操作要求具有在运行时写入(并可能从中读取)的可执行文件的功能。据我所知这是不可能的。 虽然可以根据预先接收的用户输入来更改正在运行的可执行文件的行为(例如视频游戏),但无法将这些输入直接存储到exe中。

视频游戏将玩家的进度(玩家的输入结果)存储到文件outside the running .exe中。

因此,您必须将数据存储在.exe文件的外部文件中。 我通常使用谷歌协议缓冲区来做到这一点。 here可以很好地说明它们。

它们是免费的,易于使用且受C ++支持。

它们比XML等其他格式要好。 这里提到了一些优点

  

协议缓冲区在XML上有许多advantages,用于序列化结构化数据。

协议缓冲区:。

  
      
  1. 更简单
  2.   
  3. 小3到10倍
  4.   
  5. 快20到100倍
  6.   
  7. 含糊不清
  8.   
  9. 生成易于编程使用的数据访问类
  10.   

答案 2 :(得分:0)

管理出生日期

正如我在my other answer中所解释的(您应该在此之前阅读),您不想在.exe文件的内部 中保存数据。 但是我猜测您希望将用户的出生日期(和其他数据)从一次运行保留到下一次。这个答案主要集中在“用户的出生日期”方面,并猜测您的问题是XY problem(您真正关心的是出生日期,而不是覆盖可执行文件)。

因此,您决定将它们保留在某个位置(但在可执行文件的外部之外)。那可能是一个文本文件;可能使用JSONYAML格式,或者您正确定义的其他一些文本file format,在某些文档中以EBNF表示法指定;或二进制文件(协议缓冲区可能为suggested by P.W,或者某些sqlite“数据库文件”,或者您自己需要适当记录的二进制格式)。正确记录正在使用的文件格式非常重要。

只需打开fopen,即可轻松处理文本文件(定义明确的格式)。首先,您需要定义定义良好的文件路径,也许就这么简单

#define MYAPP_DATA_PATH "mydata.txt"

或更佳

const char* myapp_data_path = "mydata.txt";

(实际上,最好使用某个绝对文件路径来从各种working directories运行程序,并提供一些重新定义它的方法,例如通过程序选项,即command-line arguments)< / p>

您可能还需要组织一些数据结构(也许是全局变量MyData global_data;)以保留该数据。在C ++中,您将定义一些class MyData;,并且希望它至少具有诸如void MyData::add_birth_date(const std::string& person, const std::chrono::time_point& birthdate);void MyData::remove_birth_date(const std::string& person);之类的成员函数。也许您会有更多的课程,例如class Person;等...

使用文本格式

因此,如果文件global_data存在,则您的应用程序首先填充mydata.txt (否则,您的global_data保持其空的初始状态)。很简单,您将拥有一些初始化功能,例如:

void initial_fill_global_data(void) {
  std::ifstream input(myapp_data_path);
  // the file opening could have failed.... then we return immediately
  if (!input || !input.good() || input.fail()) 
     return;

当然,您需要解析input。使用众所周知的parsing技术可以适当调用global_data.add_birth_date。请注意,对于JSON格式,您会找到很好的C ++库(例如jsoncpp)来简化操作。

在退出应用程序之前,应保存该文件。因此,您将调用save_global_data函数,将mydata.txt的内容输出到MyData文件中。顺便说一句,您甚至可以向std::atexit注册它。

函数initial_fill_global_datasave_global_data可以是您的static类的成员函数(也许是MyData的成员函数)。

您可能希望程序lock数据文件。这样,运行程序的两个进程就不会受到破坏。这是特定于操作系统的(例如Linux上的flock(2))。


使用sqlite数据库文件

我还建议将您的数据保存在sqlite数据库文件中。阅读一些sqlite tutorial并参考sqlite C & C++ interface参考文档。然后,您需要考虑一个设计良好的database schema。而且,您不再需要将所有数据保留在内存中,因为sqlite能够管理big amount数据(许多GB),甚至更多的内存。

显然,您需要一个全局数据库指针。因此,声明一些全局sqlite3*db;。当然,myapp_data_path现在是一些"mydata.sqlite"的路径。您的main首先使用

打开该数据库(并在必要时创建一个空数据库)
int opsta = sqlite3_open(myapp_data_path, &db);
if (opsta != SQLITE_OK) {
   std::cerr << "failed to open database " << myapp_data_path
             << " with error#" << opsta << "=" << sqlite_errstr(opsta) 
             << std::endl;
   exit (EXIT_FAILURE);
} 

如果数据库不存在,则将其创建为空。在这种情况下,您需要在其中定义适当的表和索引。我的第一个建议可能很简单

char* errormsg = NULL;
int errcod = sqlite3_exec(db, 
    "CREATE TABLE IF NOT EXISTS data_table ("
    " name STRING NOT NULL UNIQUE,"
    " birthdate INT"
    ")", 
    &errormsg);
if (errcod != SQLITE_OK) { 
    std::cerr << "failed to create data_table " << errormsg << std::endl;
    exit(EXIT_FAILURE);
}

当然,您需要考虑一些更聪明的数据库模式(实际上,您想要几个表,一些database normalization,并且您应该在自己的表头上聪明地添加indexes表格),然后prepare进行查询(将其转换为sqlite_stmt-s)。


显然,您不应该将数据保存在可执行文件中。在上述所有方法中,您的myapp程序的行为均符合您的期望。第一次运行时,它会初始化磁盘上myapp可执行文件的某些数据-外部-(如果该数据丢失)。下次,它将重用并更新该数据。但是该myapp可执行文件在运行时永远不会被重写。