C的“真实”变量大小有用吗?


9

一直以来,我一直将C的积极特性(实际上是它的实现,如gcc,clang等)打动我的一件事是,它在运行时不会在您自己的变量旁边存储任何隐藏信息。我的意思是,例如,如果您想要类型为“ uint16_t”的变量“ x”,则可以确定“ x”将仅占用2个字节的空间(并且不会携带任何隐藏信息,例如其类型等) )。同样,如果要使用100个整数的数组,则可以确保它与100个整数一样大。

但是,我越想为此功能提出具体的用例,就越想知道它实际上是否具有任何实际优势。到目前为止,我唯一能想到的就是它显然需要更少的RAM。对于有限的环境(例如AVR芯片等),这无疑是一个巨大的优势,但是对于日常的台式机/服务器用例而言,这似乎无关紧要。我正在考虑的另一种可能性是,它对于访问硬件或映射内存区域(例如,用于VGA输出等)可能很有用/至关重要。

我的问题:没有此功能,是否有任何具体的领域无法实现或只能非常繁琐地实现?

附言:请告诉我您是否有更好的名字!;)



@gnat我想我知道您的问题是什么。因为可能会有多个答案,对不对?好吧,我知道这个问题可能不适合stackexchange的工作方式,但是老实说,我不知道该在哪里询问……
Thomas Oltmann

1
@lxrec RTTI存储在vtable中,而对象仅存储指向vtable的指针。此外,如果类型已经具有vtable,则它们仅具有RTTI,因为它们具有virtual成员函数。因此RTTI绝不会增加任何对象的大小,它只会使二进制文件增大一个常数。

3
@ThomasOltmann每个具有虚拟方法的对象需要一个vtable指针。没有它,您将无法使用功能虚拟方法。此外,您明确选择使用虚拟方法(因此使用vtable)。

1
@ThomasOltmann您似乎很困惑。它不是指向带有vtable指针的对象的指针,而是对象本身。即,T *大小始终相同,并且T可能包含指向vtable的隐藏字段。而且,没有C ++编译器将vtable插入不需要它们的对象中。

Answers:


5

有很多好处,显而易见的好处是在编译时可以确保诸如函数参数之类的东西与传入的值匹配。

但是我认为您正在询问运行时发生了什么。

请记住,编译器将创建一个运行时,该运行时将有关数据类型的知识嵌入到其执行的操作中。内存中的每个数据块可能都不是自我描述的,但是代码固有地知道该数据是什么(如果您正确完成了工作)。

在运行时,情况与您想象的有些不同。

例如,声明uint16_t时不要假设仅使用了两个字节。根据处理器和字对齐方式的不同,它可以在堆栈中占用16、32或64位。您可能会发现短裤阵列消耗的内存比您预期的要多得多。

在某些情况下,您需要以特定的偏移量引用数据,这可能会带来问题。在具有不同处理器体系结构的两个系统之间通过无线链接或文件进行通信时,会发生这种情况。

C允许您使用位级别的粒度指定结构:

struct myMessage {
  uint8_t   first_bit: 1;
  uint8_t   second_bit: 1;
  uint8_t   padding:6;
  uint16_t  somethingUseful;
}

该结构为三字节长,定义为从奇数偏移量开始的短整数。还需要打包它,以使其完全符合您的定义。否则,编译器将对成员进行字对齐。

编译器将在后台生成代码,以提取该数据并将其复制到寄存器中,以便您可以执行有用的操作。

现在您可以看到,每当我的程序访问myMessage结构的成员时,它将知道如何准确地提取它并对其进行操作。

当在具有不同版本软件的不同系统之间进行通信时,这可能变得成问题并且难以管理。您必须仔细设计系统和代码,以确保双方对数据类型的定义完全相同。在某些环境中,这可能是非常具有挑战性的。在这里,您需要一个更好的协议,该协议应包含自我描述的数据,例如Google的Protocol Buffers

最后,您很想问一问这在台式机/服务器环境中的重要性。这实际上取决于您计划使用多少内存。如果您正在执行类似图像处理的操作,则可能最终会占用大量内存,这可能会影响应用程序的性能。在内存受限且没有虚拟内存的嵌入式环境中,这绝对始终是一个问题。


2
“您可能会发现短裤阵列消耗的内存比您预期的要多得多。” 在C语言中这是错误的:保证数组以无间隙的方式包含它们的元素。是的,数组需要正确对齐,就像single一样short。但这是数组开始的一次性要求,其余部分由于是连续的,因此会自动正确对齐。
cmaster-恢复莫妮卡

另外,填充的语法是错误的,应该是uint8_t padding: 6;,就像前两位一样。或者,更明确地说,只是评论//6 bits of padding inserted by the compiler。如您所写,该结构的大小至少为9个字节,而不是3个字节。
cmaster-恢复莫妮卡

9

您碰到了有用的唯一原因之一:映射外部数据结构。它们包括内存映射的视频缓冲区,硬件寄存器等。 它们还包括在程序外部完整传输的数据,例如SSL证书,IP数据包,JPEG图像,以及几乎任何在程序外部具有持久寿命的数据结构。


5

C是一种低级语言,几乎是一种便携式汇编程序,因此它的数据结构和语言构造接近金属(数据结构没有隐藏的成本-除了硬件和ABI施加的填充,对齐和大小限制之外)。因此,C确实本来就没有动态类型。但是,如果需要,可以采用一种约定,即所有值都是以某种类型信息(例如some ...)开头的集合。使用-s和(对数组状的东西)柔性阵列构件中还含有数组的大小。enumunionstruct

(在C语言中进行编程时,您有责任定义,记录和遵循有用的约定-特别是前置条件和后置条件以及不变量;C动态内存分配还需要明确约定哪些人应该使用free某些堆积的malloc内存区域)

因此,要表示装箱的整数,字符串或某种类似于Scheme符号或值向量的值,则从概念上讲,您将使用带标记的并集(实现为指针的并集)-始终从类型kind开始-,例如:

enum value_kind_en {V_NONE, V_INT, V_STRING, V_SYMBOL, V_VECTOR};
union value_en { // this union takes a word in memory
   const void* vptr; // generic pointer, e.g. to free it
   enum value_kind_en* vkind; // the value of *vkind decides which member to use
   struct intvalue_st* vint;
   struct strvalue_st* vstr;
   struct symbvalue_st* vsymb;
   struct vectvalue_st* vvect;
};
typedef union value_en value_t;
#define NULL_VALUE  ((value_t){NULL})
struct intvalue_st {
  enum value_kind_en kind; // always V_INT for intvalue_st
  int num;
};
struct strvalue_st {
  enum value_kind_en kind; // always V_STRING for strvalue_st
  const char*str;
};
struct symbvalue_st {
  enum value_kind_en kind; // V_SYMBOL
  struct strvalue_st* symbname;
  value_t symbvalue;
};
struct vectvalue_st {
  enum value_kind_en kind; // V_VECTOR;
  unsigned veclength;
  value_t veccomp[]; // flexible array of veclength components.
};

获取某些值的动态类型

enum value_kind_en value_type(value_t v) {
  if (v.vptr != NULL) return *(v.vkind);
  else return V_NONE;
}

这是对向量的“动态转换”:

struct vectvalue_st* dyncast_vector (value_t v) {
   if (value_type(v) == V_VECTOR) return v->vvect;
   else return NULL;
}

以及向量中的“安全访问器”:

value_t vector_nth(value_t v, unsigned rk) {
   struct vectvalue_st* vecp = dyncast_vector(v);
   if (vecp && rk < vecp->veclength) return vecp->veccomp[rk];
   else return NULL_VALUE;
}

通常,您将static inline在某些头文件中定义上面的大多数简短函数。

顺便说一句,如果您可以使用Boehm的垃圾收集器,则可以使用某些高级(但不安全的)样式相当容易地进行编码,并且可以使用几种Scheme解释器。可变矢量构造器可以是

value_t make_vector(unsigned size, ... /*value_t arguments*/) {
   struct vectvalue_st* vec = GC_MALLOC(sizeof(*vec)+size*sizeof(value));
   vec->kind = V_VECTOR;
   va_args args;
   va_start (args, size);
   for (unsigned ix=0; ix<size; ix++) 
     vec->veccomp[ix] = va_arg(args,value_t);
   va_end (args);
   return (value_t){vec};
}

如果你有三个变量

value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;

您可以使用它们从它们构建向量 make_vector(3,v1,v2,v3)

如果您不想使用Boehm的垃圾收集器(或设计自己的垃圾收集器),则在定义析构函数并记录谁,如何以及何时应使用free-d 时应格外小心;看到这个例子。因此,您可以使用malloc(但要对其失败进行测试)而不是GC_MALLOC上面的方法,但是您需要仔细定义和使用一些析构函数void destroy_value(value_t)

C的强度应足够低,以使上面的代码成为可能,并定义您自己的约定(尤其是您的软件)。


我想你误解了我的问题。我不想在C中进行动态键入。我很好奇C的这一特定属性是否具有实际用途。
Thomas Oltmann '16

但是,您指的是C的确切属性是什么?C数据结构非常接近金属,因此没有隐藏的成本(对齐和大小限制除外)
Basile Starynkevitch

正是这样:/
Thomas Oltmann '16

C被发明为一种低级语言,但是当启用优化功能时,如gcc之类的编译器会使用一种语言,该语言使用低级语法,但不能可靠地提供对平台提供的行为保证的低级访问。人们需要的sizeof使用malloc和memcpy的,但使用了票友地址计算可能不会在“现代” C.支持
supercat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.