【问题标题】:int * vs char * initializationint * vs char * 初始化
【发布时间】:2016-06-26 04:32:54
【问题描述】:

看看下面的 C 代码。

char * str1 = "hello";

它创建一个分配给只读内存的 char 数组,并将 str1 分配为指向第一个元素的指针。由于是只读的,内存不能在没有分段错误的情况下更改。在此声明中:

int * p = 1;

p 现在分配为 1,实际上可以重新分配给不同的值而不会发生分段错误。为什么是这样?仅在字符串文字的情况下使内存只读是编译器的决定吗?

【问题讨论】:

  • "p 现在分配为 1" 不,p 指向地址 1,这可能是无效的。
  • 不是这样的,你自己试试吧。您甚至可以打印 p 指向的地址。我会说同样的话,但事实并非如此。
  • @Nwah 你确定吗? cpp.sh/3rob
  • int * p = 1; => segfault(第一次引用p
  • int *p = 3333;想象一下,你说 p 现在指向地址 3333。如果你这样做之后 *p = 9;稍后这意味着您说请继续地址 3333 并在那里写 9。但是地址 3333 必须有效,否则您正在写信到“不属于您”的区域

标签: c


【解决方案1】:

你似乎误解了很多事情。

首先在这里:

char * str1 = "hello";

没有人限制你去做

str1 = "hello again";

稍后。它仍然是正确的。然而,你不能做的是:

str1[0] = 'a'; // This you can't do- because you aren't allowed to modify string literal

所以这意味着你不应该改变字符串文字,但你可以让指针指向不同的对象。

以整数为例:

int * p = 1;

上面一行不好,因为p 现在指向内存地址 1,取消引用它是个坏主意。

在功能上,charint 指针的行为基本相同,只是您不能修改字符串文字,这是唯一的限制。

如果同时存在charint 指针,您可以重新分配指针。

并且(不包括 char 指针指向字符串文字的情况)您还可以更改它们指向的对象的内容。例如

int x = 1;
int *y = &x;
*y = 19; // Fine, now x has value 19

char a = 'b';
char *c = &a;
*c = 'r'; // Fine, now a has value 'r'

总而言之,我会这样说:charint 指针和所有指针通常“行为”相同:您可以为它们分配地址并更改它们指向的对象(授予它们指向有效内存),仅在字符串文字的情况下,如果 char 指针恰好指向字符串文字,则不允许将 the object 更改为char 指针指向。

【讨论】:

  • int *p = avalue; 常用于有内存映射设备的嵌入式编程中。
  • 我刚刚重读了一遍,发现你说 int * p 分配了一个地址,猜我们又回到了第 1 格?
  • @cup ok 可能只是一般来说直接将一些内存地址分配给指针似乎不是一个好主意
  • @Nwah 请再看一次,更新的答案包含一些细节
  • @Nwah 请尝试在某些资源上再次读取指针,请记住 char 和 int 指针 - 以及所有指针 - 具有相同的行为,可以指向某处并且可以更改它们指向的对象。只是限制是如果 char 指针指向它,则不能更改字符串文字。
【解决方案2】:

int * p = 1;

p 现在分配为 1。

错了。 p 现在指向内存地址 1。

这可能会导致各种问题,首先是您没有正确分配从该地址开始的任何内存块。

更糟糕的是,即使您以某种方式设法在地址 1 分配了一个内存块,因为该地址未与 int 对齐(即 1 不能被 sizeof int 整除),您的代码是容易出现未对齐的加载/存储操作。

如果您的平台(即指定的编译器 + 底层硬件架构)不支持这些操作,那么任何通过p 读取或写入的尝试都可能导致:

  • 良好情况下的总线(又名分段)故障
  • 在糟糕的情况下未确定的结果

前者比后者好,因为如果发生总线故障,至少您会立即意识到问题所在,而对于未确定的结果,可能会在稍后执行程序的某个时间点出现一些意外行为,从而导致任由调试会话之神摆布。

【讨论】:

  • 确实,我认为该值已正确分配。为什么p = 2实际上将p赋给了值2和内存地址000000000023FE40?是不是因为编译器认为这是创建一个新的 int 并将其地址分配给 p?请记住,我正在执行 printf("%p", &p),这看起来很奇怪。
  • @Nwah:我认为你在p 的值和p 的地址(即&p 的值)之间混合。如果p 是静态和/或全局的,则p 的内存地址由编译器确定。否则(如果是非静态本地的),那么每次调用函数时p的内存地址可能都不一样。请注意,这些(包括我提到的未对齐的加载/存储操作)都不是由 C 语言标准规定的,即它受编译器实现的影响。
  • @Nwah:另外,请注意,您只能接受一个答案作为正确答案(我注意到您在接受另一个答案后接受了我的答案)...所以选择您认为的答案你的问题最好。
  • printf("%d %p", p, &p) 打印 2 和 000000000023FE40。 &p 在每次执行时总是有这个地址。这适合第一种情况吗?请记住,程序在 main 中只有 3 行变量赋值。
  • @Nwah:“p的内存地址可能每次调用函数时都不一样”。
【解决方案3】:

好问题,先比较一下:

char* str1 = "hello";
const char* const str1 = "hello";
char str1[] = "hello";

char* str1 = "hello";str1 中是一个指向char 类型的指针。您可以进行指针运算,str1++ 将编译,这会将数据放入内存的只读部分(常量数据)。并使用 str1[0]='a';将导致运行时错误(但编译正常)。
const char* const str1 = "hello"; 中的数据和指针是 const:使用 str1[0]='a'; 会导致编译错误。
char str1[] = "hello"; str1 中是一个数组(常量指针)。使用str1[0]='a'; 是可以的,但是使用str1++ 会导致编译错误:

#include <stdio.h>

void reverse(char *p){
    char c;
    char* q = p;
    while (*q) q++; 
    q--; // point to the end
    while (p < q) {
        c = *p;
        *p++ = *q;
        *q-- = c;
    }
}

int main(){
    char s[]  = "DCBA";
    reverse( s);
    printf("%s\n", s); // ABCD
}

第二个:
指针的好处是:指针也是一个变量,就像 'int' 有一个区别:
使用带有 int 的 i++ 只会在 i 上加一,但带有指针的 p++ 会增加一个底层类型的内存大小,例如int32_t* p; p++ 将 4(int32_t 大小)添加到 p 值。
p++ 增加 p 变量内存本身,而不是它可能指向的内存。(这是您正在寻找的答案)。

现在比较一下:

int ary[] = { 1, 2, 3 };
//int* p2 = { 1, 2, 3 };
int* p = ary;
const int* const q = ary;

在此示例中:

#include <stdio.h>

int main() {
    int ary[] = { 1, 2, 3 };
    //int* p2 = { 1, 2, 3 };
    int* p = ary;
    const int* const q = ary;

    ary[0] = 10; // ok
    p[0] = 100; // ok
    //q[0] = 11; // error
    printf("%x\n", p);
    printf("%d\n", p[0]); // 100
    return 0;
}

虽然char* str1 = "hello"; 有效,但int* p2 = { 1, 2, 3 }; 无效。 char* p2 = { 'h', 'e', 'l', 'l', 'o', 0 }; 也无效。所以char* str1 = "hello"; 是编译时的特殊编译器技巧,它将“hello”放入只读内存空间。
我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-15
    相关资源
    最近更新 更多