如何访问内存地址的内容?

huangapple go评论66阅读模式
英文:

How to access the content of a memory address?

问题

问题:
file1.cpp中,我将一些数据保存到内存地址0x0364DF00中。

file2.cpp中,如何访问内存地址(0x0364DF00)的内容?

我尝试过的方法:

我尝试添加一些代码来解决这个问题,但是没有成功。

#include <iostream>
using namespace std;

int main() {
    int* address = (int*)0x0364DF00;
    cout << *address;
    return 0;
}

这只是一个实验,我不知道是否可行。

英文:

Question:

In file1.cpp, I save some data to memory with memory address 0x0364DF00.

In file2.cpp, how to access the content of the memory address (0x0364DF00)?

What I tried

I tried adding some code to solve this problem, but it didn't work.

#include &lt;iostream&gt;
using namespace std;

int main() {
	int* address = (int*)0x0364DF00;
    cout &lt;&lt; address(&amp;address);
	return 0;
}

This is just an experiment. I don't know if it is possible.

答案1

得分: 7

你可以使用一种称为共享内存的概念来实现这个。

使用共享内存,你可以共享一个类似下面的结构体:

struct Data {
    std::atomic<int> counter;
};

请注意,由于你将在不同的核心之间共享数据,你需要进行适当的同步。在这种情况下,我们使用std::atomic

然后,你需要一种机制将这个内存映射到一个共享位置,这是与操作系统相关的,但通常会提供一个文件作为内存的命名位置,并提供一个大小(用于调整文件大小)。一旦打开了文件,就可以将其映射到内存,并返回一个指向内存位置的指针。

通常,生产者(或创建者)会像下面这样给出文件名和大小:

// 这段代码可以在 file1.cpp 中
void child() {
    MappedFile file;
    if (!file.open("/tmp/file", 1024)) return;
    Data* data = reinterpret_cast<Data*>(file.data());
    for (unsigned j = 0; j <= 10; ++j) {
        data->counter = j;
        ::usleep(1000000);
    }
}

然后,另一个进程可以查看文件,获取其大小,并像下面的代码一样将其完全映射到内存。映射位置返回的指针指向与前一个指针相同的物理内存。然而,由于这两个函数在不同的进程上运行,它们的虚拟映射将不同,因此它们的数值很可能也不同。

// 这段代码可以在 file2.cpp 中
void parent() {
    MappedFile file;
    while (!file.open("/tmp/file")) {}
    Data* data = reinterpret_cast<Data*>(file.data());
    int last = 0;
    while (data->counter < 10) {
        if (last != data->counter) {
            std::cout << "Counter:" << data->counter << std::endl;
            last = data->counter;
        }
    }
}

内部实现是与操作系统相关的,但在Linux上,它可能如下所示。这是我为测试编写的一个示例,因此它不是生产质量的,甚至没有完全测试(但对于这个示例可以工作)。

首先定义一个类,其中包含一个整数来保存文件描述符、文件大小和共享内存位置。

struct MappedFile {
    MappedFile() {}
    ~MappedFile() { close(); }
private:
    int _fid = -1;
    void* _addr = nullptr;
    std::size_t _mapsize = 0;
};

然后创建一个方法来打开/创建具有给定大小的文件。这是“创建者”。它使用O_CREAT标志打开文件,以便如果文件不存在,则创建文件。然后将文件截断为提供的大小,该大小首先舍入为页面大小(在Linux上通常为4k)- 这是mmap()的要求。然后将文件映射到内存,并将指针返回给用户。

    bool open(const std::string& filename, std::size_t filesize) {
        int fid = ::open(filename.c_str(),
            O_CREAT | O_RDWR, S_IRWXU | S_IRWXG);
        if (fid < 0) {
            std::cerr << strerror(errno) << std::endl;
            std::cerr << "Could not create file " << filename << std::endl;
            return false;
        }
        long mapsize = ((filesize - 1) / PAGESIZE + 1) * PAGESIZE;
        int res = ftruncate(fid, mapsize);
        if (res != 0) {
            std::cerr << strerror(errno) << std::endl;
            std::cerr << "Could not truncate file" << std::endl;
            ::close(fid);
            return false;
        }
        void* addr = mmap(nullptr, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fid, 0);
        if (addr == MAP_FAILED) {
            std::cerr << "Could not memory map region" << std::endl;
            ::close(fid);
            return false;
        }
        close();
        _fid = fid;
        _addr = addr;
        _mapsize = mapsize;
        return true;
    }

然后有另一个方法依赖于文件已经被创建的事实。请注意,存在一种竞争条件的可能性,即在文件被创建后但在其被截断为正确大小之前,消费者打开文件。因此,我们检查文件大小以查看是否为零。其余部分与之前相同,只是我们不调整文件大小。

    bool open(const std::string& filename) {
        struct stat sb;
        if (stat(filename.c_str(), &sb) != 0) {
            return false;
        }
        size_t filesize = sb.st_size;
        if (filesize == 0) return false;
        int fid = ::open(filename.c_str(), O_RDWR, S_IRWXU | S_IRWXG);
        if (fid < 0) {
            std::cerr << strerror(errno) << std::endl;
            std::cerr << "Could not open file " << filename << std::endl;
            return false;
        }
        void* addr = mmap(nullptr, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fid, 0);
        if (addr == MAP_FAILED) {
            std::cerr << "Could not memory map region" << std::endl;
            ::close(fid);
            return false;
        }
        close();
        _fid = fid;
        _addr = addr;
        _mapsize = filesize;
        return true;
    }

请注意,我说的是“生产者”和“消费者”,但这两个进程都可以对映射内存进行读写访问。

销毁对象对于两个进程来说是相同的,只需取消映射内存并关闭文件。

    void close() {
        if (_addr != nullptr) {
            ::munmap(_addr, _mapsize);
            ::close(_fid);
        }
        _addr = nullptr;
        _fid = -1;
        _mapsize = 0;
    }

这个测试的示例驱动程序使用fork()生成一个单独的进程,并继续共享信息。

int main() {
    pid_t pid = fork();
    if (pid == 0) child();
    else parent();
}

这将产生以下输出:

Counter:1
Counter:2
Counter:3
Counter:4
Counter:5
Counter:6
Counter:7
Counter:8
Counter:9
Counter:10

完整代码在Godbolt上:https://godbolt.org/z/T8a5enzje

英文:

You can do this using a concept called shared memory.

Using shared memory you could share say (as an example) a struct like the following:

struct Data {
    std::atomic&lt;int&gt; counter;
};

Note that because you will be sharing data across different cores, you need to properly synchronize. In this case we use std::atomic.

You then need a mechanism to map this memory to a shared location, which is OS-specific but in general would consist in providing a file as a named-location for the memory and a size (to resize the file). Once opened the file can be memory-mapped and a pointer to a memory location is returned to the user.

In general the producer (or creator) is given the file name and size like this:

// And this in file1.cpp
void child() {
    MappedFile file;
    if ( !file.open( &quot;/tmp/file&quot;, 1024 ) ) return;
    Data* data = reinterpret_cast&lt;Data*&gt;(file.data());
    for ( unsigned j=0; j&lt;=10; ++j ) {
        data-&gt;counter = j;
        ::usleep( 1000000 );
    }
}

Then the other process can then look at the file, get its size and memory map it on its entirety like in the following code. The pointer returned by the mapped location points to the same physical memory as the previous pointer. However, because these two functions are running on different process, their virtual mapping will be different and thus their numeric value will very likely be different too.

// This can be in file2.cpp
void parent() {
    MappedFile file;
    while ( !file.open( &quot;/tmp/file&quot; ) ) {}
    Data* data = reinterpret_cast&lt;Data*&gt;( file.data() );
    int last = 0;
    while ( data-&gt;counter &lt; 10 ) {
        if ( last!=data-&gt;counter ) {
            std::cout &lt;&lt; &quot;Counter:&quot; &lt;&lt; data-&gt;counter &lt;&lt; std::endl;
            last = data-&gt;counter;
        }
    }
}

The internal implementation is OS-specific but on Linux it would look like the following. This is an example I wrote for testing so it's not production quality or even tested fully (but works for this example).

First define a class with an integer to hold the file descriptor, the file size and the shared memory location.

struct MappedFile {
    MappedFile() {}
    ~MappedFile() { close(); }
private: 
    int _fid = -1;
    void* _addr = nullptr;
    std::size_t _mapsize = 0;
};

Then create one method to open/create the file with a given size. This is the "creator". It opens the file with the O_CREAT flag such that if the file does not exists, it is created. Then it truncates the file to the size provided, which is first rounded to a page size (typically 4k on Linux) - this is an mmap() requirement. Then the file is memory mapped and a pointer is returned to the user.

    bool open( const std::string&amp; filename, std::size_t filesize ) {
        int fid = ::open( filename.c_str(), 
            O_CREAT|O_RDWR, S_IRWXU | S_IRWXG);
        if ( fid&lt;0 ) {
            std::cerr &lt;&lt; strerror(errno) &lt;&lt; std::endl;
            std::cerr &lt;&lt; &quot;Could not create file &quot; &lt;&lt; filename &lt;&lt; std::endl;
            return false;
        }
        long mapsize = ((filesize-1)/PAGESIZE + 1 )*PAGESIZE;
        int res = ftruncate( fid, mapsize );
        if ( res!=0 ) {
            std::cerr &lt;&lt; strerror(errno) &lt;&lt; std::endl;
            std::cerr &lt;&lt; &quot;Could not trucate file&quot; &lt;&lt; std::endl;
            ::close(fid);
            return false;
        }
        void* addr = mmap( nullptr, mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, fid, 0 );
        if ( addr==MAP_FAILED ) {
            std::cerr &lt;&lt; &quot;Could not memory map region&quot; &lt;&lt; std::endl;
            ::close(fid);
            return false;
        }
        close();
        _fid = fid;
        _addr = addr;
        _mapsize = mapsize;
        return true;
    }

Then there is another method that relies on the fact that the file is already created. Note that there is a possibility of a race condition where the consumer opens the file after it is created but before it is truncated to the correct size. For this reason, we check the file size to see if it is zero. The rest is the same with the exception that we do not resize the file.

    bool open( const std::string&amp; filename ) {
        struct stat sb;
        if ( stat( filename.c_str(), &amp;sb ) != 0 ) {
            return false;        
        }
        size_t filesize = sb.st_size;
        if ( filesize == 0 ) return false;
        int fid = ::open( filename.c_str(), O_RDWR, S_IRWXU | S_IRWXG);
        if ( fid&lt;0 ) {
            std::cerr &lt;&lt; strerror(errno) &lt;&lt; std::endl;
            std::cerr &lt;&lt; &quot;Could not open file &quot; &lt;&lt; filename &lt;&lt; std::endl;
            return false;
        }
        void* addr = mmap( nullptr, filesize, PROT_READ|PROT_WRITE, MAP_SHARED, fid, 0 );
        if ( addr==MAP_FAILED ) {
            std::cerr &lt;&lt; &quot;Could not memory map region&quot; &lt;&lt; std::endl;
            ::close(fid);
            return false;
        }
        close();
        _fid = fid;
        _addr = addr;
        _mapsize = filesize;
        return true;
    }

Note that I say "producer" and "consumer" but both the processes have read-write access to the mapped memory.

Destroying the object is equal for both processes and as simple as unmapping the memory and closing the file.

    void close() {
        if ( _addr!=nullptr ) {
            ::munmap( _addr, _mapsize );
            ::close(_fid);
        }
        _addr = nullptr;
        _fid = -1;
        _mapsize = 0;
    }

A sample driver for this test spawns a separate process with fork() and proceeds to share information

int main() {
    pid_t pid = fork();
    if ( pid == 0 ) child(); else parent();
}

This produces

Counter:1
Counter:2
Counter:3
Counter:4
Counter:5
Counter:6
Counter:7
Counter:8
Counter:9
Counter:10

full code on Godbolt: https://godbolt.org/z/T8a5enzje

huangapple
  • 本文由 发表于 2023年8月9日 06:10:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76863490.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定