Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/156.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 索引到结构中是否合法?_C++_C_Struct - Fatal编程技术网

C++ 索引到结构中是否合法?

C++ 索引到结构中是否合法?,c++,c,struct,C++,C,Struct,不管代码有多“糟糕”,并且假设对齐等在编译器/平台上不是问题,这是未定义的行为还是不正确的行为 如果我有这样一个结构:- struct data { int a, b, c; }; struct data thing; 作为(&thing.a)[0],(&thing.a)[1],和(&thing.a)[2],访问a,b和c是否合法 在每一种情况下,在每一个编译器和平台上我都尝试过它,在每一个设置中我都尝试过它“工作”。我只是担心编译器可能没有意识到b和东西[1]是同一个东西,存储到“

不管代码有多“糟糕”,并且假设对齐等在编译器/平台上不是问题,这是未定义的行为还是不正确的行为

如果我有这样一个结构:-

struct data
{
    int a, b, c;
};

struct data thing;
作为
(&thing.a)[0]
(&thing.a)[1]
,和
(&thing.a)[2]
,访问
a
b
c
是否合法

在每一种情况下,在每一个编译器和平台上我都尝试过它,在每一个设置中我都尝试过它“工作”。我只是担心编译器可能没有意识到b东西[1]是同一个东西,存储到“b”的东西可能被放在寄存器中,而东西[1]从内存中读取了错误的值(例如)。在每一次尝试中,我都做了正确的事情。(我当然意识到这证明不了什么)

这不是我的代码;这是我必须处理的代码,我感兴趣的是这是坏的代码还是坏的代码,因为不同的代码会极大地影响我更改代码的优先级:)


标记C和C++。我对C++感兴趣,但C也不同,只是为了兴趣。

不,在C中,这是未定义的行为,即使没有填充。p> 导致未定义行为的因素超出了访问范围1。当您有一个标量(结构中的成员a、b、c)并试图将其用作array2来访问下一个假设元素时,您会导致未定义的行为,即使该地址上碰巧有另一个相同类型的对象

但是,您可以使用struct对象的地址并计算特定成员的偏移量:

struct data thing = { 0 };
char* p = ( char* )&thing + offsetof( thing , b );
int* b = ( int* )p;
*b = 123;
assert( thing.b == 123 );
必须对每个成员单独执行此操作,但可以将其放入类似于数组访问的函数中


1(引用自:ISO/IEC 9899:201x 6.5.6加法运算符8)
如果结果指向数组对象的最后一个元素后一个元素,则 不得用作计算的一元*运算符的操作数

2(引用自:ISO/IEC 9899:201x 6.5.6添加剂操作员7)
在这些运算符中,指向不是对象元素的对象的指针 数组的行为与指向长度为1的数组的第一个元素的指针相同 对象的类型作为其元素类型。

它是非法的1。这是C++中的一个未定义的行为。

您以数组的方式使用成员,但这里是C++标准所说的(强调我的):

…数组类型的对象包含一个连续分配的非空N集 类型T的子对象

但是,对于成员来说,没有这样的连续性要求:

。。。;实施一致性要求可能导致两个相邻的 成员之间不能立即分配

虽然上面两个引文应该足以说明为什么索引为<代码>结构> <代码>不是C++标准定义的行为,我们来举一个例子:看表达式<代码>(和.To.a)[2 ] < /代码>关于下标操作符:< /P> 后缀表达式后跟方括号中的表达式是 后缀表达式。其中一个表达式应为glvalue类型 “T数组”或“指向T的指针”类型的PR值,另一个应 是非范围枚举或整数类型的prvalue。结果是 属于“T”类。类型“T”应为完全定义的对象类型。66 表达式
E1[E2]
((E1)+(E2))

深入研究上面引用的粗体文本:关于向指针类型添加整数类型(请注意此处的重点)

将具有整数类型的表达式添加到 指针,则结果具有指针操作数的类型如果 表达式
P
指向数组的元素
x[i]
对象
x
对于n个元素,表达式
P+J
J+P
(其中
J
具有 值
j
)指向(可能是假设的)元素
x[i+j]
如果
0≤ i+j≤ n
否则,行为未定义

注意if子句的数组要求;否则,请在上面的引文中选择。表达式
(&thing.a)[2]
显然不符合if子句的条件;因此,未定义的行为


另一方面:虽然我已经在各种编译器上对代码及其变体进行了广泛的实验,并且它们没有在这里引入任何填充(它是有效的);从维护的角度来看,代码非常脆弱。在执行此操作之前,您仍然应该断言实现是连续分配成员的。并保持在边界内:-)。但它的行为仍不明确

其他答案提供了一些可行的解决方法(具有定义的行为)



正如在评论中正确指出的那样,,在我之前的编辑中不适用。谢谢@2501和@M.M


< P > 1:请参阅巴里对这个问题的回答,以唯一的一个合法的案例来访问<代码>事物。结构的成员< /代码>通过这个部分。C++中的

< p>如果你真的需要它,创建操作符[]:< /p>
struct data
{
    int a, b, c;
    int &operator[]( size_t idx ) {
        switch( idx ) {
            case 0 : return a;
            case 1 : return b;
            case 2 : return c;
            default: throw std::runtime_error( "bad index" );
        }
    }
};


data d;
d[0] = 123; // assign 123 to data.a
它不仅保证工作,而且使用更简单,您不需要编写不可读的表达式
(&thing.a)[0]

注意:这个答案是假设您已经有了一个带有字段的结构,并且您需要通过索引添加访问权限。如果速度是一个问题,您可以改变结构,这可能会更有效:

struct data 
{
     int array[3];
     int &a = array[0];
     int &b = array[1];
     int &c = array[2];
};
此解决方案将更改结构的大小,以便您也可以使用以下方法:

struct data 
{
     int array[3];
     int &a() { return array[0]; }
     int &b() { return array[1]; }
     int &c() { return array[2]; }
};

这是非法的,但有一个解决办法:

struct data {
    union {
        struct {
            int a;
            int b;
            int c;
        };
        int v[3];
    };
};
现在,您可以为
struct data {
  int a, b, c;
};

typedef int data::* data_int_ptr;

data_int_ptr arr[] = {&data::a, &data::b, &data::c};

data thing;
thing.*arr[0] = 123;
const int x = 7;
std::cout << x << '\n';
auto ptr = (int*)&x;
*ptr = 2;
std::cout << *ptr << "!=" << x << '\n';
std::cout << ptr << "==" << &x << '\n';
int& operator[](std::size_t);
int const& operator[](std::size_t) const;
struct array_data
{
  int arr[3];

  int &operator[]( unsigned idx ) {
      // assert(idx <= 2);
      //idx = (idx > 2) ? 2 : idx;
      return arr[idx];
  }
  int &a(){ return arr[0]; } // TODO: const versions
  int &b(){ return arr[1]; }
  int &c(){ return arr[2]; }
};
# asm from g++6.2 -O3
int getb(array_data &d) { return d.b(); }
    mov     eax, DWORD PTR [rdi+4]

void setc(array_data &d, int val) { d.c() = val; }
    mov     DWORD PTR [rdi+8], esi

int getidx(array_data &d, int idx) { return d[idx]; }
    mov     esi, esi                   # zero-extend to 64-bit
    mov     eax, DWORD PTR [rdi+rsi*4]
int cpp(data *d, int idx) {
    return (*d)[idx];
}

    # gcc6.2 -O3, using `default: __builtin_unreachable()` to promise the compiler that idx=0..2,
    # avoiding an extra cmov for idx=min(idx,2), or an extra branch to a throw, or whatever
    cmp     esi, 1
    je      .L6
    cmp     esi, 2
    je      .L7
    mov     eax, DWORD PTR [rdi]
    ret
.L6:
    mov     eax, DWORD PTR [rdi+4]
    ret
.L7:
    mov     eax, DWORD PTR [rdi+8]
    ret
c(type_t*, int):
    movsx   rsi, esi                   # sign-extend this time, since I didn't change idx to unsigned here
    mov     eax, DWORD PTR [rdi+rsi*4]
#include <iostream>
#include <ctime>

template <typename T>
class Proxy {
public:
    T &a, &b, &c;
    Proxy(T* par) : a(par[0]), b(par[1]), c(par[2]) {}
    Proxy* operator -> () { return this; }
};

struct Data {
    int ar[3];
    template <typename I> int& operator [] (I idx) { return ar[idx]; }
    template <typename I> const int& operator [] (I idx) const { return ar[idx]; }
    Proxy<int>       operator -> ()       { return Proxy<int>(ar); }
    Proxy<const int> operator -> () const { return Proxy<const int>(ar); }
    int* begin()             { return ar; }
    const int* begin() const { return ar; }
    int* end()             { return ar + sizeof(ar)/sizeof(int); }
    const int* end() const { return ar + sizeof(ar)/sizeof(int); }
};

// Nci returns an unpredictible int
inline int Nci() {
    static auto t = std::time(nullptr) / 100 * 100;
    return static_cast<int>(t++ % 1000);
}

#if 1
int main() {
    Data d = {Nci(), Nci(), Nci()};
    for(auto v : d) { std::cout << v << ' '; }
    std::cout << "\n";
    std::cout << d->b << "\n";
    d->b = -5;
    std::cout << d[1] << "\n";
    std::cout << "\n";

    const Data cd = {Nci(), Nci(), Nci()};
    for(auto v : cd) { std::cout << v << ' '; }
    std::cout << "\n";
    std::cout << cd->c << "\n";
    //cd->c = -5;  // error: assignment of read-only location
    std::cout << cd[2] << "\n";
}
#else
int main() {
    Data d = {Nci(), Nci(), Nci()};
    for(auto v : d.ar) { std::cout << v << ' '; }
    std::cout << "\n";
    std::cout << d.ar[1] << "\n";
    d->b = -5;
    std::cout << d.ar[1] << "\n";
    std::cout << "\n";

    const Data cd = {Nci(), Nci(), Nci()};
    for(auto v : cd.ar) { std::cout << v << ' '; }
    std::cout << "\n";
    std::cout << cd.ar[2] << "\n";
    //cd.ar[2] = -5;
    std::cout << cd.ar[2] << "\n";
}
#endif
char index_data(const struct data *d, size_t index) {
  assert(sizeof(*d) == offsetoff(*d, c)+1);
  assert(index < sizeof(*d));
  char buf[sizeof(*d)];
  memcpy(buf, d, sizeof(*d));
  return buf[index];
}