ANSI C:声明一个“字符串数组”类型的指针

3
我有一个类似于这样的结构
struct {
  int    id;
  char   str00[10];
  char   str01[10];
  char   str03[10];
  char   str04[10];
  ...
}   myStructure;

所有的strXX都有相同的大小。我想通过一个数组(strArray)来访问它们,就像这样:

strcpy(strArray[i], strValue);

如何声明strArray?

我使用了以下代码:

char   (*strArray)[][10] = (void *)&myStructure.str00;

它正在工作,但我必须像这样编写strcpy

strcpy((*strArray)[i], strValue);

...而且我不喜欢这样 :-)

有没有其它方式来声明strArray?

感谢您的建议和帮助。


无论如何,指向数组的指针都需要被解引用。你需要习惯这种语法或使用普通指针。 - diapir
2
为什么你想在一开始就把成员定义成一个数组的数组,而不是单独命名它们呢? - Eric Postpischil
2个回答

3

你差点就对了,正确的指针类型是char (*ptr)[10]

直觉上,你可以使用这个指针类型来遍历结构体成员,但这样做会导致未定义行为。因为严格来说,指针只能指向单个项目或数组,如果我们使用指针算术运算来超出这个单个项目/数组,就会引起未定义行为。

为了演示数组指针算术,我仍然会提供一个例子:

// BAD CODE, it relies on undefined behavior!

#include <stdio.h>
#include <string.h>

typedef struct {
  int    id;
  char   str00[10];
  char   str01[10];
  char   str02[10];
  char   str03[10];
} myStructure;


int main(void)
{
  myStructure ms = { 0 };
  char (*ptr)[10] = &ms.str00;

  for(size_t i=0; i<4; i++)
  {
    strcpy(ptr[i], "hello ");
    strcat(ptr[i], (char[]){i+'0', '\0'});
  }

  puts(ms.str00);
  puts(ms.str01);
  puts(ms.str02);
  puts(ms.str03);

  return 0;
}

正确的解决方案是使用union,这样您可以单独访问成员,或作为数组访问:

typedef union {
  struct              // anonymous struct, requires a standard C compiler
  {
    char str00[10];
    char str01[10];
    char str02[10];
    char str03[10];
  };
  char array[4][10];
} str_t;

typedef struct {
  int    id;
  str_t  str;
} myStructure;


strcpy(ms.str.array[i], ...); // access as array
puts(ms.str.str00); // access as individual item

1
我感觉填充仍然使得这两种解决方案依赖于未定义的行为和不可移植性。 - DarkDust
不幸的是,尽管union选项确实避免了UB,但不能保证它能够满足OP的要求,因为在struct中数组之间可能存在填充,但在2D数组的元素之间则没有。 - John Bollinger
实际上,结构体填充的存在是由具体实现定义的,并取决于特定系统可能的对齐要求。 - Lundin

2

按照要求定义strArray最干净的方法是将其定义为指向myStructure中数组(即第一个元素)的指针数组:

char *strArray[] = { myStructure.str00, myStructure.str01, myStructure.str03, myStructure.Str04, … };

使用这个定义,strArray[i]会被初始化为结构体对应的成员,例如myStructure.str01。请注意,myStructure.str01会自动转换为它的第一个元素的指针,所以strArray[i]是一个指向数组中第一个char的指针。
然后,strArray[i][j]是数组i中的jchar
(顺便说一句,在你的示例代码中跳过了str02。我不知道为什么,但已在上面的代码中保留它。)
另一种方法是使用联合体,可以通过多种方式实现,其中一种是:
struct
{
    int    id;
    union
    {
        struct
        {
            char   str00[10];
            char   str01[10];
            char   str03[10];
            char   str04[10];
            ...
        };
        char strArray[number of arrays][10];
    };
} myStructure;

通常来说,这是一种糟糕的设计,因为它会让人感到不必要的困惑。(虽然由于单独定义数组之间的填充,这有可能在技术上失败,但可以使用断言来确保这不会发生,或者更确切地说是在发生时检测出来。)

大多数情况下,我们只需将字符串定义为一个数组的数组:

struct
{
    int id;
    char str[number of arrays][10];
} my Structure;

然后成员总是通过索引来引用,例如myStructure.str[1],而不是通过个别名称引用,例如myStructure.str01


与其说是因为它过于复杂不可取,不如说union方法之所以不可取,更多的是因为由于struct成员之间可能存在填充的可能性,不能保证其按预期工作。虽然在某些符合规范的实现上可能会按预期工作,但在其他实现上可能会出现问题,这使得它变得非常糟糕。 - John Bollinger
@JohnBollinger:已经在几乎同时的编辑中解决了。 - Eric Postpischil
我看到了你的编辑,但我认为你低估了这个问题。此外,我只勉强同意断言的建议用法适用于该功能。我更愿意将它们的使用保留在断言程序行为方面,而不是C实现的特征方面。无论如何,你有我的支持。 - John Bollinger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接