【问题标题】:C functions multiple results multiple typesC 函数 多种结果 多种类型
【发布时间】:2013-05-20 12:23:51
【问题描述】:

我正在编写一个 SCPI 解析器(作为一个库)。 lib用户的设备或应用程序功能需要能够输出多种类型的多个结果(不是混合类型,即A类型的多个结果 B类型的多个结果)。

我首先想到了一个由执行控件传递给设备函数的 void 指针数组(如果设备函数类似于unsigned int deviceFunction (double inArg1, bool inArg2, void ** outResults);),但在这里查看几个 Q 似乎 void ptrs 不是推荐因为,当然,当我的响应格式化程序查看结果数据时,它不知道要转换为什么类型。

我研究过使用联合和结构,类似于 https://stackoverflow.com/a/3852192/1292918,但我不确定当设备函数需要报告多个结果时这是否是最佳方式。

也许我刚刚把我的想法打乱了。这样做有没有通用的方式或习惯用法?

(注意这是 C,不是 C++)

【问题讨论】:

  • 有多种方法可以解决这个问题。然而,一个更大的问题是关于要返回的对象数量的已知信息。如果它足够小以至于始终可以使用最大空间量,那么使用数组(联合数组,带有另一个对象来指示类型,可能一起打包在一个结构中)是合适的。如果它很大并且变化很大,那么动态分配空间可能会更好,那么就存在调用者或被调用函数是否应该提供空间,以及哪个函数负责释放空间的问题。
  • @EricPostpischil 这是针对嵌入式环境的,因此动态分配并不是一个真正的选择——内存分配应该在编译时知道。尽管大多数 SCPI 命令将返回有限数量的结果或一两个复合结果,但拥有固定大小的数组并不是什么问题,因此可以使用最大数量的空间。感谢您的意见。

标签: c pointers void-pointers


【解决方案1】:

这是一个例子。可以根据您的喜好或需求进行多种变化:

struct MyStruct
{
    enum MyNum { Integer, Float, String } WhatTypeIsHere;
    size_t HowMany;
    union
    {
        int Integer;
        float Float;
        const char *String;
    } u[MaximumNumberOfElement];
};

要设置,给定一些x,即struct MyStruct

x.WhatTypeIsHere = Float;
x.HowMany = NumberOfObjectsThisTime;
for (i = 0; i < NumberOfObjectsThisTime; ++i)
    x.u[i].Float = some value;

Integer 或其他类型的设置类似。

阅读:

switch (x.WhatTypeIsHere)
{
    case Float;
        for (i = 0; i < x.HowMany; ++i)
            Use x.u[i].Float for something…;
        break;
    case Integer:
        …
}

您的函数可以声明为使调用者将struct MyStruct 传递给它,然后该函数将其填充,或者该函数定义一个本地struct MyStruct 并按值返回它。 (通常,当您执行后者时,C 的底层实现中发生的情况是:调用者为 struct MyStruct 分配空间并将其地址传递给负责填充它的被调用函数。如果编译器正确优化, 性能与第一个选项相当,即使源代码看起来像被调用的函数是按值返回结构体。)

关于结构和联合的组织方式有多种选择。您可以使用数组联合而不是联合数组,并且可以传递有关单独返回的类型的信息,而不是在结构内部的 enum 中传递,并且已知元素的数量可以单独返回或通过其他一些信息,例如数组中的标记值。对于小型、简单的用途,这些变化可能没有太大区别,它们由您来选择。

【讨论】:

    【解决方案2】:

    根据 Eric 的评论,我想出了以下代码,它使用 union 返回一个复杂的结构。在这种情况下不会出现提到的“别名规则”错误。

    struct Result {
      int type;
    };
    
    struct Data1
    {
      int type;
      char* str;
      int val;
    };
    
    struct Data2 {
      int type;
      int arr[4];
    };
    
    union StructTestUnion
    {
      struct Result res;
      struct Data1 data1;
      struct Data2 data2;
    };
    
    union StructTestUnion getRandomResult(void) {
      static int cnt = 0;
      union StructTestUnion resVal;
      switch (cnt) {
        case 0:
          resVal.data1.type = 1;
          resVal.data1.str = "struct data 1";
          resVal.data1.val = 123;
          break;
        case 1:
          resVal.data2.type = 2;
          resVal.data2.arr[0]=1;
          resVal.data2.arr[1]=2;
          resVal.data2.arr[2]=3;
          resVal.data2.arr[3]=4;
          break;
      }
      cnt++;
      if (cnt==2) cnt = 0;
    
      return resVal;
    }
    
    int main() {
      union StructTestUnion resVal;
    
      int a =0;
      for (a =0; a<4; a++) {
        resVal = getRandomResult();
        printf("%d: %d ", a, resVal.res.type);
        switch ( resVal.res.type ) {
          case 1:
            printf("str=[%s] val=[%d]\n", resVal.data1.str, resVal.data1.val);
            break;
          case 2:
            printf("arr=[%d,%d,%d,%d]\n",resVal.data2.arr[0], resVal.data2.arr[1],resVal.data2.arr[2], resVal.data2.arr[3]);
            break;
        }
      }
      return 0;
    }
    

    【讨论】:

    • 为什么不拥有一个包含“类型”字段的struct(例如int)和不同类型特定返回数据的union(如果需要,可以是struct )?在每个struct 中复制type 然后将它们推入union 似乎很愚蠢。
    • @NikBougalis 如果您进一步传递相同的结构,也作为联合,您将不得不手动重新填充类型。这样,无论您对数据做什么,类型都会保持不变。
    • 很公平。我仍然觉得这更尴尬,但至少背后有一个很好的理由。
    • 谢谢大家,我最终选择了 Eric 的解决方案,但这确实帮助我理解了它是否来自 :)
    【解决方案3】:

    你仍然可以使用void 双指针,但是你必须找到一种方法来告诉调用者它实际上是什么类型。例如,通过使用 return 从函数中返回它,或者使用另一个引用类型参数,例如int *outResultsType.

    然后让调用者正确使用数据。

    【讨论】:

      【解决方案4】:

      你可以像这样创建一个通用结构:

      struct Generic {
        int type;
      }
      

      还有几个带有数据的真实返回结构,但第一个字段未修改:

      struct Data1 {
        int type; // == TYPE_DATA1 == 1
        char* aCharVal;
        double aDoubleVal;
      }
      
      struct Data2 {
        int type; // == TYPE_DATA2 == 2
        int* anArray;
        int arraySize;
      }
      

      这样您就可以返回结构,将其转换为struct Generic,检查类型并继续进行。

      这种方法的一个问题是“取消引用类型双关指针会破坏严格的别名规则”。

      【讨论】:

      • 既然您知道这会破坏别名规则,为什么不使用为处理在运行时确定类型的对象而设计的适当语言功能,即联合,而不是转换指针?
      • @EricPostpischil 我没有那样使用联合。我没想到你能做到。谢谢你的想法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-21
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      • 2019-09-28
      • 2016-09-20
      • 2017-10-02
      相关资源
      最近更新 更多