内存中的单一副本

时间:2016-05-19 12:06:57

标签: c

编译器可以选择在内存中使用单个副本表示所有相同的字符串文字。例如:

    /*Case 1*/
    char *s1 = "foo";
    char *s2 = "foo";

    printf("s1 points to : %p\n", s1);
    printf("s2 points to : %p\n", s2);

给出

s1 points to : 0x40063c
s2 points to : 0x40063c

然而,

    /*Case 2*/
    char *s1 = "foo";
    char *s2 = (char[]){'f', 'o', 'o', '\0'};

    printf("s1 points to : %p\n", s1);
    printf("s2 points to : %p\n", s2);

给出

s1 points to : 0x40070c
s2 points to : 0x7ffe2866fcd0

这是完全可以的,因为s1指向一些不变的东西,而s2则没有。

但是,有没有办法告诉编译器,在case2中,我们实际上希望将s2指向一些常量数据 - 但保持其声明样式 - 以便如果s2指向的数据与指向的数据相同通过s1,编译器可以在内存中继续使用单个数据副本。

我试过

const char* s2=(char[]){'f','o','o','\0'};

没有运气。

5 个答案:

答案 0 :(得分:5)

如果最终目标是获得s1 == s2,那就这么说(s2 = s1)而不是试图欺骗编译器这样做。

答案 1 :(得分:2)

C标准说

  

7个字符串文字和带有 const限定类型的复合文字,   不需要指定不同的对象.1101)

和脚注101

  

101)这允许实现共享字符串文字的存储空间   和具有相同或重叠的常数复合文字   表示。

因此,至少应该将复合文字定义为

const char* s2=( const char[]){'f','o','o','\0'};
                 ^^^^^

const char* s2=( const char[]){ "foo" };
                 ^^^^^

然而,最好比较字符串本身然后比较它们的地址,因为不同的编译器可以有不同的选项来设置此功能,或者甚至可以不存在这样的选项。

答案 2 :(得分:1)

为两个看似不同的字符串文字获取相同地址的原因是称为“字符串池”的优化器概念。如果编译器/链接器可以告诉代码的多个位置存在相同的字符串,那么它只会分配一次。它可以这样做,因为字符串文字是只读的。

与复合文字(char[]){'f', 'o', 'o', '\0'};不同,后者在某些读/写内存中分配,例如在堆栈中。

如果你自己制作复合文字const

(const char[]){'f', 'o', 'o', '\0'};

它应该在.rodata部分结束,但编译器可能无法意识到它等同于字符串文字。如果没有,就没有字符串池,你最终会在内存中重复。

然而:const char* s2=(char[]){'f','o','o','\0'};不会使复合文字本身成为只读,它只会影响指针。

答案 3 :(得分:1)

不,没有。字符串的创建与编译器的视图完全不同。

以这个小程序为例:

int main(void)
{
    char *s1 = "foo";
    char s2[] = {'f','o','o','\0'};
    char s3[] = "foo";
    return 0;
}

所有三个字符数组可能看起来都是相同的。但他们不是。

s1是一个指向字符数组的指针,这是编译器决定放置它的地方(通常是.data.cstring,等等。

另一方面,

s2是一个长度为4的字符数组。整个4个字节将落在堆栈上。

s3等于s2

没有安全的方法来实现您尝试实现的目标,(非优化)编译器甚至可能为第一种情况生成两个不同的字符串。

获得行为的唯一安全方法是通过显式设置指向您希望它们指向的字符串的指针。

#include <assert.h>
static char FOOSTR[] = "foo";

int main(void)
{
    char *s1 = FOOSTR;
    char *s2 = FOOSTR;
    assert(s1 == s2);
    return 0;
}

请注意,FOOSTR可以修改。

答案 4 :(得分:0)

首先,我们注意到您的module ActiveRecord class Relation def pluck_all(*args) args.map! do |column_name| if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}" else column_name.to_s end end relation = clone relation.select_values = args klass.connection.select_all(relation.arel).map! do |attributes| initialized_attributes = klass.initialize_attributes(attributes) attributes.map do |key, attribute| klass.type_cast_attribute(key, initialized_attributes) end end end end end s1不一样。 s2是常数,而"foo"则不是。但即便如此,通常在函数内部创建数组时,即使它是一个字符串,编译器通常也会在堆栈中即时生成它。特别是因为字符串“foo”可以只用一条指令放在堆栈上:

(char[]){'f', 'o', 'o', '\0'}

但是。让我们看看我们是否可以说服编译器给我们你想要的东西。

$ cat foo2.c
void bar(const char *);
void
foo(void)
{
    char *x = (char[]){'f', 'o', 'o', '\0'};
    bar(x);
}
$ cc -O3 -S foo2.c
$ cat foo2.s
[...]
        subq    $16, %rsp
        movl    $7303014, -4(%rbp)      ## imm = 0x6F6F66
        leaq    -4(%rbp), %rdi
        callq   _bar

在一台机器上:

#include <stdio.h>

const char *s5 = (const char[]){'f', 'o', 'o', '\0'};

int
main(int argc, char **argv)
{
    char *s1 = "foo";
    char *s2 = "foo";
    char *s3 = (char[]){'f', 'o', 'o', '\0'};
    const char *s4 = (const char[]){'f', 'o', 'o', '\0'};

    printf("%p\n%p\n%p\n%p\n%p\n", s1, s2, s3, s4, s5);
    return 0;
}

没有运气。即使$ cc -o foo foo.c && ./foo 0x400654 0x400654 0x7fff6cc15030 0x7fff6cc15040 0x400650 $ cc -O3 -o foo foo.c && ./foo 0x400620 0x400620 0x7fff20200960 0x7fff20200970 0x400634 $ cc -v [...] gcc version 4.8.5 20150623 (Red Hat 4.8.5-4) (GCC) 不在堆栈上并且是一个常量gcc也不够聪明,不能确定它与s5

相同

我们试试另一台机器:

"foo"

啊哈。通过足够的优化(-O1不起作用),clang可以解决这个问题。

编译器需要花费时间和精力来确定两个常量是相同的,我猜gcc不会打扰。