【问题标题】:Subtract two elements' (from the same structure) addresses?减去两个元素(来自同一结构)的地址?
【发布时间】:2014-12-04 03:52:33
【问题描述】:

我正在编写一个程序,我需要计算结构元素的两个地址之间的差异。对不起,如果不清楚,有些代码可能更明确。

代码中有一些 MPI 元素,但这不是我的问题。

typedef struct Str {
  std::vector<int> itab;
  std::vector<char> ctab;
} s_str;

int main(int argc, char **argv)
{
  s_str *struc1 = new s_str();

  MPI_Datatype Strtype;
  int count = 2;
  int lengths[2] = { 8, 8 };
  MPI_Aint disp[2];
  disp[0] = &struc1->itab - &struc1;
  disp[1] = &struc1->ctab - &struc1;
  MPI_Datatype types[2] = { MPI_INT, MPI_CHAR };
  MPI_Type_create_struct(count, lengths, disp, types, &Strtype);
  MPI_Type_commit(&Strtype);

  (...)
}

问题发生在以下几行:

MPI_Aint disp[2];
disp[0] = &struc1->itab - &struc1;
disp[1] = &struc1->ctab - &struc1;

首先,MPI_Aint 是“包含任何有效地址的 C 类型”(取自文档)。我在这里尝试做的是计算 struc1->itab 和 struc1 地址之间的差异。

这是我遇到的错误:

tmp2.cpp: In function ‘int main(int, char**)’:
tmp2.cpp:30:30: erreur: invalid operands of types ‘std::vector<int>*’ and ‘s_str** {aka Str**}’ to binary ‘operator-’
tmp2.cpp:31:30: erreur: invalid operands of types ‘std::vector<char>*’ and ‘s_str** {aka Str**}’ to binary ‘operator-’

我想我没有正确访问地址,因为这里涉及的类型是std::vector&lt;int&gt;*s_str**。正确的方法是什么?

更多详细信息:我需要使用 MPI-I/O 将其写入文件。它告诉 MPI 每个 proc 需要多少空间来编写其整个数据集。所以基本上,我只想使用 MPI-I/O 将用户定义结构的内容写入文件中。原因如下。

【问题讨论】:

  • 由于struc1 已经一个地址(一个指针),通过在它上面使用address-of操作符你得到地址的地址(即一个指向指针)。如果你通过使用例如“修复”它&amp;struc1-&gt;itab - struc1 然后你没有得到地址,你得到了结构内itab 成员的偏移量,已经有一个标准的offsetof 宏。
  • 哦,即使使用“修复”(即不使用结构的地址运算符),您仍然可能会遇到错误,因为类型并不真正兼容,您必须强制转换指针。或者,如果您想要获取偏移量,请使用 offsetof 宏。
  • @JoachimPileborg offsetof 是 UB,除非该类是标准布局 - 并且 std::vector 可能不是标准布局,在这种情况下 Str 也不是。
  • 请注意,即使您可以通过强制转换为 void* 来获取向量的地址,向量的实际元素也会放在内存中的其他位置*
  • 如果您在 C++ 中使用 MPI,请考虑使用 Boost.MPI 之类的东西,它会为您处理所有这些讨厌的序列化问题。

标签: c++ struct mpi


【解决方案1】:

我认为你的推理有点错误,即使你给出了向量所在的地址,向量的实际元素也可能放在别处。

例如尝试例如

s_str struc1;
struc1.itab.push_back(0);
printf( "struc1.itab %p\n", (void*)&(struc1.itab) );
printf( "struc1.ctab %p\n", (void*)&(struc1.ctab) );
printf( "struc1.itab[0] %p\n", (void*)&(struc1.itab[0]) );
printf( "struc1 %p\n", (void*)&struc1 );

struc1.itab 000000000022fde0
struc1.ctab 000000000022fdf8
struc1.itab[0] 0000000000327f40
struc1 000000000022fde0

也许您最初有一个具有两个普通 c 数组的结构?

typedef struct 
{
  int itab[100];
  char ctab[100];
} struc1;

那么你在做什么会更有意义。

【讨论】:

  • 我认为这是对的。 OP 几乎肯定想要向量中数据的第一个元素的地址,而不是向量本身的地址。
【解决方案2】:

试试这个:

 disp[0] = ((char *) &(struc1->itab)) - ((char *) struc1);
 disp[1] = ((char *) &(struc1->ctab)) - ((char *) struc1);

【讨论】:

    【解决方案3】:

    这是我用的。它适用于我的项目,但我不知道为什么这两种解决方案都有效,因为我认为它们的含义不同:

    MPI_Aint disp[2] = { 0, (uintptr_t)&(struc1->ctab[0]) - (uintptr_t)&(struc1->itab[0]) };
    MPI_Aint disp[2] = { 0, sizeof (std::vector<int>) + sizeof (std::vector<char>) };
    // Why do both of these solutions work ?
    

    有人可以确认这不是针对我的情况,并且这些方法并不完全陌生/糟糕吗?

    【讨论】:

      【解决方案4】:

      便携式 MPI 方式是:

      MPI_Aint base;
      
      MPI_Get_address(&struc1, &base);
      MPI_Get_address(&struc1->itab, &disp[0]);
      disp[0] -= base;
      MPI_Get_address(&struc1->ctab, &disp[1]);
      disp[1] -= base;
      

      无论如何,这不起作用,因为std::vector 为其在堆上的元素分配内存。由于堆分配可能最终在进程地址空间中完全随机的位置,因此应该使用稍微不同的方法:

      MPI_Aint disp[2];
      
      // N.B. both displacements are ABSOLUTE addresses
      MPI_Get_address(&struc1->itab[0], &disp[0]);
      MPI_Get_address(&struc1->ctab[0], &disp[1]);
      
      MPI_Type_create_struct(count, lengths, disp, types, &Strtype);
      MPI_Type_commit(&Strtype);
      
      MPI_Send(MPI_BOTTOM, 1, Strtype, ...);
      
      MPI_Type_free(&Strtype);
      

      结构类型是使用绝对地址构造的,因此MPI_BOTTOM 必须在MPI_Send 调用中使用(MPI_BOTTOM 表示缓冲区地址是进程地址空间的底部)。这仅适用于发送结构类型的一个实例,因此该类型在发送调用后立即被销毁。原则上,为每个要发送的结构实例创建一个新的数据类型并不是什么大问题,因为发送数据比创建和销毁类型的操作成本更高。

      【讨论】:

        猜你喜欢
        • 2015-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-27
        • 1970-01-01
        相关资源
        最近更新 更多