数组中的array[1]发生了什么?

4

我有一大块代码,旨在处理一个数组。在当前项目中,只会有一个元素,因此我将变量声明为char array [1],而不是char。这样我就不需要修改我的代码和冒险添加任何错误,并且如果要求增加,可以轻松地增加它。

看起来编译没有问题,但我对底层发生的事情产生了好奇,我是否浪费了内存?这是否增加了额外的处理时间?编译器是否会优化掉它,以至于没有任何区别?

有人能解释一下使用这种方式可能存在的任何缺点吗?

我使用c和c ++,它们之间有什么不同吗?

4个回答

9

听起来是一个不错的策略,而且没有什么缺点。在C或C++中,使用大小为1的数组不会浪费内存。大小为1的数组所占用的内存与同类型变量所占用的内存相同。

可能编译器生成的代码微不足道地不够高效,但这真的不值得担心。


7
标准规定,您可以获取非数组对象的地址并将其视为大小为1的数组(因此可以获取指向越过末尾的指针)。
请参阅C++11标准的§5.7.4节:
“对于这些运算符,一个指向非数组对象的指针与一个以该对象类型作为元素类型的长度为1的数组的第一个元素的指针行为相同。”

3

首先,您的代码是有效的,但如果您关心缺点,我可以看到以下问题:

使用数组时,如果您不小心地循环遍历数组,则增加了越界访问的可能性。

另一个缺点是数组无法与多态交互。有时您尝试将派生对象存储到基类类型的数组中,对象将被切片,您可能没有注意到。

因此,我不会编写array[1]代码。希望这回答了您的一些问题。


在可能出现循环偏移错误的情况下,我会给代码打上“+1”的标记,特别是在C语言中。 - Bartek Banachewicz
4
好的观点,但由于原帖中提到基于数组的代码已经存在,因此我认为尝试 array[1] 这个想法可能会考虑到可能出现的越界错误,这对尚未发现的一般性 array[n] 代码中的漏洞来说是一个额外加分项。 - us2012
数组如何导致切片,而非数组变量所导致的方式?考虑A派生自B。A a; B b, b2[1]; b = a; b2[0] = a; 这两个赋值都会导致切片。在B b3& = a;中不会发生切片,但这是一个引用,与开头的问题无关。多态性也是同样的道理。 - Anonymous Coward
我记得当使用(gcc / glibc)的“-D_FORTIFY_SOURCE”选项时,尝试故意超出char [n]数组大小时会出现一些奇怪的问题。这个选项显然告诉GCC插入运行时哨兵/边界检查。 - Mark Nunberg

0

在这里,我们面对着两个代码块。

  • 一个大的代码块,旨在接受一个数组并对其进行处理。
  • 一段使用前面的代码块和一些数据的代码片段。

结构化你的代码。

大的代码块应该是一个函数,可能分为几个子函数。其他代码将调用此函数。

函数的参数。数组或单个字符。

  (a) void work( char  c );
  (b) void work( char& c );
  (c) void work( const char v[], size_t size);
  (d) void work(       char v[], size_t size);

如果工作类型不适合使用数组,则应使用选项(a)和(b)。但这不是情况。
如果工作适用于数组,则应使用选项(c)和(d)。

因此,请使用数组。

保存数据的变量。数组或单个字符。

如果只需要保存单个字符,则使用单个非数组字符。您仍然可以调用数组函数。

char c;
c = 'b';
work( &c, 1 );
//////
char a[1];
a[0] = 'b';
work( a, 1 );

工作函数将单个变量和数组视为大小为1的数组。代码在两种情况下都可以正常工作,而且没有效率问题。

测试

让我们看看真实代码是否符合我的先前说法。

#include <iostream>
#include <ctime>
#include <vector>
#include <cstddef>
#include <chrono>

using namespace std;

unsigned long miliTime()
{
    return std::chrono::system_clock::now().time_since_epoch() /
           std::chrono::milliseconds(1);
}
// An hypotetical work function with arrays
void workArray( char v[], size_t size )
{
  for ( size_t n=0; n<size; ++n )
  {
    // large block of code
    for ( int i=0; i<1000; ++i )
    {
      v[n] += 3 + i;
      if (v[n] == '3' )
        v[n] = 'J' - v[n];
      v[n] = toupper( v[n] ) + '-';
    }
  }
}

// Same function just for a single character
void workSingle( char& c )
{
  // large block of code
  for ( int i=0; i<1000; ++i )
  {
    c += 3 + i;
    if (c == '3' )
      c = 'J' - c;
    c = toupper( c ) + '-';
  }
}

int main(void)
{
  const long int repeats =1000000;
  long int n;
  unsigned long start;
  double dif;

  start = miliTime();
  char c;
  c = 'b';
  for ( n=0; n<repeats; ++n)
    workArray( &c, 1 );
  dif = miliTime() - start;
  cout << "Result = " << c << endl;
  cout << "Non-array var passed to array code = " << dif << " ms" << endl;

  start = miliTime();
  char a[1];
  a[0] = 'b';
  for ( n=0; n<repeats; ++n)
    workArray( a, 1 );
  dif = miliTime() - start;
  cout << "Result = " << a[0] << endl;
  cout << "Array var passed to array code = " << dif << "ms" << endl;

  start = miliTime();
  char c2;
  c2 = 'b';
  for ( n=0; n<repeats; ++n)
    workSingle( c2 );
  dif = miliTime() - start;
  cout << "Result = " << c2 << endl;
  cout << "Non-array var passed to non-array code = " << dif << "ms" << endl;

  start = miliTime();
  char a2[1];
  a2[0] = 'b';
  for ( n=0; n<repeats; ++n)
    workSingle( a2[0] );
  dif = miliTime() - start;
  cout << "Result = " << a2[0] << endl;
  cout << "Array var passed to non-array code = " << dif << "ms" << endl;
}

使用以下命令在gcc-4.7下编译并在我的计算机上执行:

g++ -O2 -Wall -std=c++11 x.cpp -o x.out && ./x.out

我得到了这个输出:
结果 = z
非数组变量传递给数组代码 = 5520毫秒
结果 = z
数组变量传递给数组代码 = 5515毫秒
结果 = z
非数组变量传递给非数组代码 = 5203毫秒
结果 = z
数组变量传递给非数组代码 = 5203毫秒

正如预期的那样,结果总是相同的。 对于两种实现,将数组或非数组变量传递给工作函数没有显着的差异。

workSingle比workArray快6%。
外部循环的执行(在workSingle中不存在)不太可能是原因,因为内部循环执行1000次。原因可能是访问v[n]比访问c慢,因为需要间接寻址。
但是,如果您将内部循环中的1000更改为从std :: cin读取的全局变量,则workSingle实际上会比workArray提供更慢的时间!

某些优化、缓存未命中或其他低级别的东西可能是原因。除非时间非常关键,您愿意进入汇编级别,否则我不会牺牲workArray的可重用性以换取workSingle的不确定效率。

结论。

将您的变量声明为非数组,因为它只需要保存单个字符。
将您的大段代码实现为一个接受数组参数的函数。如果太大,可以分成几个子部分。

本帖文仅在CC-BY-SA 3.0许可下向公众开放。


我绝对不会使用选项(b),因为我不希望work(foo)修改foo。这对于不熟悉work()内部工作原理的读者来说是一个地雷。如果你想修改参数,请明确说明并使用指针请求void work(char* c);work(&foo)清楚地告诉读者他应该期望foo发生变化。 - cmaster - reinstate monica
不,work(&foo)并没有告诉读者他应该期望foo会改变。你需要检查函数声明和文档。考虑使用void(const char* foo)。在C++中,通过引用而不是指针传递IN-OUT参数是常见的方式。 - Anonymous Coward

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