【问题标题】:Are the elements of an array saved continuously in memory?数组的元素是否连续保存在内存中?
【发布时间】:2015-07-19 16:51:17
【问题描述】:

我正在学习编程范式教程并进行一些与内存相关的练习。我刚刚提取了两个问题(请忘记代码的样子,对不起,这是错误的代码,但我只想从中学习指针的东西)。原始代码使 lsearch 尽可能通用,因此它使用所有指针。

  1. 我有一个关于访问数组元素的问题:

    当我们访问数组a[]的第四个元素时,我们使用*(a+3); 当我们访问字符串数组char * names[] 的第三个元素时,我们使用*(char**)((char*)names+sizeof(char*)*2)。 我们是否假设数组的元素是连续保存在内存中的?

  2. 关于访问字符串数组元素的问题2:为什么我们需要在names(基地址)前面添加(char*),我尝试不添加(char*)并且代码因分段错误而崩溃错误。我打印了(char*)的大小,是8,而sizeof(names)是32。这里为什么需要(char*)?我真的认为地址是 32 位,如果我们将其转换为 8 位,计算机应该很难找到它指向的元素。 谢谢你

#include<iostream>
#include<string>
#include<typeinfo>
int main()
{
        int a[5] = {1,2,3,4,5};
        std::cout << *(a+3) << std::endl;


        char* names[4] = {"James", "Dustin", "Rich", "Ed"};
        std::cout << *(char**)((char*)names+2*sizeof(char*)) << std::endl;
        //std::cout << sizeof(names) << " " << sizeof(char*) << std::endl;
}

这是来自 stanford 107 的原始代码。

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;

int StrCmp(void* vp1, void* vp2)
{
        char *s1 = *(char**)vp1;
        char *s2 = *(char**)vp2;
        if(strcmp(s1,s2)==0){
                return 0;
        }
        return 1;
}

char ** lsearch(void *key, void* base, int size, int elemSize, int (*cmpFn)(void *, void *))
{
        for(int i=0; i<size; ++i){
                char* addr = (char*) base + elemSize *i;
                if (cmpFn(key, addr)==0) return (char**)addr;
        }
        return NULL;
}

int main()
{
        char *notes[] = {"Ab", "F#", "B", "Gb", "D"};
        char* favorStr = "F#";
        char ** found = lsearch(&favorStr,notes,5,sizeof(char*),StrCmp);
        if(found!=NULL) cout << *(char**)found << endl;
        else cout << "not found" << endl;
}

【问题讨论】:

  • sizeof 返回字节大小,而不是位(并且您的指针显然是 64 位宽)。
  • 这个问题表明之前的研究很少,其中一些是错误的。检查标准将回答您有关连续性的问题。检查一些关于数组和/或指针的(更好的)教程将使您摆脱诸如*(char**)((char*)names+2*sizeof(char*))之类的神秘结构
  • 另外,如果你想使用 C++,你应该避免使用旧的 C 风格的字符串。并不是说它们像您的帖子声称的那样困难,但是为什么要活在过去呢?使用现代 C++ 功能,您完全不必担心事物是否连续、指针的宽度等。
  • 那不是 C 代码。还有非常糟糕的 C++ 代码。
  • @underscore_d:只是为了说明清楚:实际上也不会用 C 语言编写它。而且,这不是“旧的 C 风格字符串”,但那是 C 的工作方式。没有什么“旧的”。两种语言都有其用例,不应被视为“旧式”。实际上,C++ 的“过去”并不比 C 少很多。

标签: c++ arrays string memory


【解决方案1】:

根据 C++ 标准(8.3.4 数组)

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

从您的帖子中应用到数组定义,这意味着只有四个指向字符串文字的指针(数组的元素)被分配在连续的内存范围内。此内存范围不包括字符串文字本身。

在 C++ 中,字符串文字具有常量字符数组的类型。当它们被用作这样的初始化器时

char* names[4] = {"James", "Dustin", "Rich", "Ed"};

然后它们被隐式转换为指向数组第一个元素的const char * 类型的指针。

所以最好像这样定义数组

const char* names[4] = {"James", "Dustin", "Rich", "Ed"};

如果你想输出数组的第三个元素(考虑到它是一个指针)那么你必须写

std::cout << ( const void * ) ( names + 2 ) << std::endl;

如果你想输出这个元素指向的字符串字面量,你应该简单地写

std::cout << *( names + 2 ) << std::endl;

至于表达

*(char**)((char*)names+2*sizeof(char*)) 

然后子表达式(char*)names 重新解释char *[4] like an array pf typechar [4 * sizeof( char * )] 类型的数组。所以表达

(char*)names+2*sizeof(char*)

产生与表达式相同的值

( names + 2 )

举例说明

std::cout << ( const void * )( names + 2 ) << std::endl;

std::cout << ( void * )( (char*)names + 2*sizeof(char*) ) << std::endl;

将产生相同的输出

我认为如果运行这个演示程序会更清楚

#include <iostream>

int main() 
{
    const char* names[4] = {"James", "Dustin", "Rich", "Ed"};

    for ( size_t i = 0; i < 4; i++ ) 
    {        
        std::cout << ( const void * )( names + i ) << std::endl;
    }

    std::cout << std::endl;

    for ( size_t i = 0; i < 4; i++ ) 
    {        
        std::cout << ( const void * )*( names + i ) << std::endl;
    }

    std::cout << std::endl;

    for ( size_t i = 0; i < 4; i++ ) 
    {        
        std::cout << ( const void * )( ( char * )names + i * sizeof( char * ) ) << std::endl;
    }

    std::cout << std::endl;

    for ( size_t i = 0; i < 4; i++ ) 
    {        
        std::cout << ( const void * )*( ( const char ** )( ( const char * )names + i * sizeof( char * ) ) ) << std::endl;
    }

    std::cout << std::endl;
}

程序输出可能看起来像

0x7fffc0f639f0
0x7fffc0f639f8
0x7fffc0f63a00
0x7fffc0f63a08

0x40d884
0x40d88a
0x40d891
0x40d896

0x7fffc0f639f0
0x7fffc0f639f8
0x7fffc0f63a00
0x7fffc0f63a08

0x40d884
0x40d88a
0x40d891
0x40d896

在此程序中,表达式( names + 0 )( names + 1 )( names + 2 )( names + 3 ) 是索引为0, 1, 2, 3 的数组名称对应元素的地址。 表达式*( names + 0 )*( names + 1 )*( names + 2 )*( names + 3 ) 是存储在此元素中的值。

表达式( char * )names + i * sizeof( char * ) 其中i 在0-3 范围内 是数组元素的相同地址。和表达式 *( ( const char ** )( ( const char * )names + i * sizeof( char * ) ) ) 给出数组元素的相同值。

【讨论】:

  • 谢谢!我试过 std::cout 相同的结果
  • @daydayup 对不起。我打错了。请参阅我更新的帖子。:)
  • 谢谢@VladfromMoscow!请问,在 std::cout
  • @daydayup 使用强制转换 (char *)names 将内存范围解释为字符数组。要找到存储指向第三个字符串文字的指针的位置,您必须将等于 2 * sizeof( char * ) 的值添加到范围的地址
  • @daydayup 数组名称被隐式转换为 int * 类型。表达式 a + 3 也具有 int * 类型并指向数组的第四个元素。表达式 *( a + 3 ) 等价于 a[3] 并产生存储在元素中的值。
猜你喜欢
  • 1970-01-01
  • 2017-10-01
  • 2021-02-09
  • 2012-04-30
  • 1970-01-01
  • 2021-09-14
  • 1970-01-01
  • 2014-02-17
相关资源
最近更新 更多