指针和对c中的内存的访问。小心

时间:2019-01-03 16:12:34

标签: c pointers

仍然学习更多C,并且有些困惑。在我的参考文献中,我发现有关分配尚未初始化的指针的注意事项。他们继续举例。昨天,很棒的答案来自人们帮我指点的方式,在这里:

Precedence, Parentheses, Pointers with iterative array functions

在跟进时,我简短地询问了循环的最后一次迭代,并可能将指针指向一个不存在的位置(即由于我的引用警告该位置)。所以我回头看了看又发现了:

如果您有指针

int *pt;

然后在不初始化的情况下使用它(即,我指的是没有*pt= &myVariable之类的语句):

*pt = 606;

根据指针分配到内存中的位置,您可能会遇到糟糕的一天。我遇到麻烦的部分是在处理字符串时,可以这样:

char *str = "Sometimes I feel like I'm going crazy.";

参考文献中的内容为:“不必担心字符串在内存中的分配位置;它由编译器自动处理”。因此,无需说初始化*str = &str[0];*str = str;。意思是编译器会自动char str[n];在后​​台吗?

为什么对此处理方式不同?还是我完全误会了?

9 个答案:

答案 0 :(得分:22)

在这种情况下:

char *str = "Sometimes I feel like I'm going crazy.";

您正在初始化str以包含给定字符串文字的地址。此时,您实际上并没有取消引用任何东西。

这也很好:

char *str;
str = "Sometimes I feel like I'm going crazy.";

因为您要分配给str,而实际上并未取消引用它。

这是一个问题:

int *pt;
*pt = 606;

因为pt未初始化,然后然后被取消引用。

出于同样的原因,您也不能这样做(加上类型不匹配):

*pt= &myVariable;

但是您可以这样做:

pt= &myVariable;

之后,您可以自由使用*pt

答案 1 :(得分:16)

当您写#include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/Type.h" #include "llvm/IR/Verifier.h" using namespace llvm; LLVMContext TheContext; int main() { return 0; } 时,它等效于sometype *p = something;,而不是sometype *p; p = something;。这意味着当您使用这样的字符串文字时,编译器会弄清楚将其放在哪里,然后将其地址放在那里。

声明

sometype *p; *p = something;

等同于

char *str = "Sometimes I feel like I'm going crazy.";

答案 2 :(得分:11)

简化字符串文字可以表示为:

const char literal[] = "Sometimes I feel like I'm going crazy.";

所以表达式

char *str = "Sometimes I feel like I'm going crazy.";

在逻辑上等同于:

const char literal[] = "Sometimes I feel like I'm going crazy.";
const char *str = literal;

当然,文字没有名称。

但是您不能取消引用没有为实际对象分配内存的char指针。

/* Wrong */
char *c;
*c = 'a';
/* Wrong  - you assign the pointer with the integer value */ 
char *d = 'a';

/* Correct  */
char *d = malloc(1);
*d = 'a';

/* Correct */
char x
char *e = &x;
*e = 'b';

最后一个示例:

/* Wrong - you assign the pointer with the integer value */
int *p = 666;

/* Wrong you dereference the pointer which references to the not allocated space */
int *r;
*r = 666;

/* Correct */
int *s = malloc(sizeof(*s));
*s = 666;

/* Correct */
int t;
int *u = &t;
*u = 666;

最后一个-类似于字符串文字=复合文字:

/* Correct */
int *z = (int[]){666,567,234};
z[2] = 0;
*z = 5;

/* Correct */
int *z = (const int[]){666,567,234}; 

答案 3 :(得分:5)

做好这个例子。很好地显示了声明指针(如char *text;)和分配给指针(如text = "Hello, World!";)之间的区别。

写时:

char *text = "Hello!";

它与说的话基本相同

char *text;        /* Note the '*' before text */
text = "Hello!";   /* Note that there's no '*' on this line */

(请注意,第一行也可以写为char* text;。)

那么为什么第二行上没有*?因为text的类型为char*,所以“ Hello!”的类型也为char*。这里没有分歧。

就编译器而言,以下三行相同:

char *text = "Hello!";
char* text = "Hello!";
char * text = "Hello!";

*之前或之后的空格没有任何区别。第二行可能更易于阅读,因为它使textchar*的观点更加明确。 (但是要小心!如果在一行上声明多个变量,这种样式可能会烫伤您!)

至:

int *pt;
*pt = 606;   /* Unsafe! */

您可能会说*ptint606也是如此,但是更确切地说pt(没有*)是应该包含 int 内存指针。而*pt(带有*)是指pt(没有*)指向的内存中的 int

并且由于从未初始化过pt,因此使用*pt(分配或取消引用)是不安全的。

现在,关于线条的有趣部分:

int *pt;
*pt = 606;   /* Unsafe! */

是他们将进行编译(尽管可能会发出警告)。这是因为编译器将*pt视为int,也将606视为int,所以没有分歧。但是,按照书面规定,指针pt不会指向任何有效的内存,因此分配给*pt可能会导致崩溃,数据损坏或引爆世界末日等。

重要的是要认识到*pt不是变量(即使它经常像一个变量一样使用)。 *pt仅指地址在pt中的内存中的值。因此,*pt是否可以安全使用取决于pt是否包含有效的内存地址。如果未将pt设置为有效内存,则使用*pt是不安全的。

所以现在您可能想知道:将pt声明为int*而不是int的意义何在?

要视情况而定,但在许多情况下,没有任何意义。

在C和C ++中进行编程时,我会使用以下建议:如果可以不用声明变量就可以声明变量,那么就不应该将其声明为指针。

程序员经常在不需要时使用指针。当时,他们没有其他选择。根据我的经验,当提请他们注意不使用指针时,他们通常会说不使用指针是不可能的。当我以其他方式证明它们时,它们通常会回溯并说它们的代码(使用指针)比不使用指针的代码更有效。

(但是,并非所有程序员都这样。有些人会认识到用非指针替换指针的吸引力和简便性,并乐于更改其代码。)

当然,我不能说所有情况,但是如今C编译器通常足够聪明,可以将指针代码和非指针代码都编译为在效率方面几乎完全相同。不仅如此,而且视情况而定,非指针代码通常比使用指针的代码更有效。

答案 4 :(得分:5)

您在示例中混淆了4个概念:

  1. 声明指针。 int *p;char *str;是指针的声明
  2. 在声明处初始化指针。 char *str = "some string";声明指针对其进行初始化。
  3. 将值分配给指针。 str = "other string";将值分配给指针。类似地,p = (int*)606;会将值606分配给指针。但是,在第一种情况下,该值是合法的,并且指向字符串在静态内存中的位置。在第二种情况下,您可以为p分配一个任意地址。它可能是合法地址,也可能不是。因此,p = &myint;p = malloc(sizeof(int));是更好的选择。
  4. 为指针指向的对象分配一个值。 *p = 606;将该值分配给“ pointee”。现在取决于指针“ p”的值是否合法。如果您没有初始化指针,则它是非法的(除非您很幸运:-)。

答案 5 :(得分:3)

这里有很多很好的解释。 OP已要求

  

为什么对此处理方式不同?

这是一个公平的问题,他的意思是为什么,而不是如何

简短答案

这是设计决定。

好答案

在迁移中使用文字时,编译器有两个选择:要么将文字放置在生成的汇编指令中(也许允许可变长度的汇编指令适应不同的文字字节长度),要么将文字放置在cpu的某个位置可以到达它(内存,寄存器...)。对于int,将它们放在汇编指令上似乎是一个不错的选择,但是对于字符串...几乎所有在程序(?)中使用的字符串都太长而无法放在汇编指令上。鉴于任意长的汇编指令对通用CPU都是不利的,C设计人员已决定针对字符串优化此用例,并通过为他分配内存来节省程序员一步。这样,行为在计算机之间是一致的。

反例 只是要了解,对于其他语言,不一定必须如此,请选中this。那里(是Python),int常量实际上总是放在内存中并始终赋予 id 。因此,如果您尝试获取分配给相同文字的两个不同变量的地址,它将返回相同的id(因为它们引用的是相同文字,已经由Python加载程序放置在内存中)。需要强调的是,在Python中,id在Python的抽象机中为equivalent to an address

答案 6 :(得分:2)

每个内存字节都存储在其自己的编号信鸽孔中。该数字是该字节的“地址”。

编译程序时,它将建立一个常量数据表。在运行时,这些文件将被复制到内存 somewhere 中。因此,执行后,内存中就是字符串(这里是第100,000个字节):

@100000 Sometimes I feel like I'm going crazy.\0

编译器已生成代码,因此在创建变量str时,它将自动使用该字符串存储的地址进行初始化。因此,在此示例中,str -> 100000。这是名称​​ pointer 的来源,str实际上并不包含该字符串数据,它保存着它的地址(即一个数字),“指向”它,说:在该地址的那段数据”。

因此,如果将str视为整数,它将包含值100000

当取消引用*str = '\0'之类的指针时,它的意思是:内存str指向,将其'\ 0'放在此处

因此,当代码定义了指针但没有进行任何初始化时,它可能指向任何地方,甚至可能指向可执行文件不拥有(或拥有但无法写入)的内存。

例如:

int *pt = blah;  // What does 'pt' point at?

它没有地址。因此,如果代码尝试取消引用它,则它只是指向内存中的任何地方,这将产生不确定的结果。

但是这种情况:

int number = 605;
int *pt    = &number

*pt = 606;

是完全正确的,因为编译器已经生成了一些用于存储number的空间,现在pt包含了该空间的地址。

因此,当我们在变量上使用地址运算符&时,它将为我们提供存储变量内容的内存中的数字。因此,如果变量number恰好存储在byte 100040

int number = 605;
printf( "Number is stored at %p\n", &number );

我们将得到输出:

Number is stored at 100040

与字符串数组类似,它们实际上也只是指针。该地址是第一个元素的存储号。

// words, words_ptr1, words_ptr2 all end up being the same address
char words[] = "Sometimes I feel like I'm going crazy."
char *words_ptr1 = &(words[0]);
char *words_ptr2 = words;

答案 7 :(得分:0)

这里有很好的详细信息,还有答案。 我将发布另一个答案,也许更直接地针对OP。 改写一下:

为什么

int *pt;
*pt = 606;

不好(不适用),

char *str = "Sometimes I feel like I'm going crazy.";

可以(可行的情况下)吗?

请考虑:

  1. char *str = "Sometimes I feel like I'm going crazy.";
    

    等同于

    char *str;
    str = "Sometimes I feel like I'm going crazy.";
    
  2. int最接近“类似”工作例是(使用compound literal而不是字符串文字)

    int *pt = (int[]){ 686, 687 };
    

    int *pt;
    pt = (int[]){ 686, 687 };
    

因此,与您无法使用的案例的区别有三点:

  1. 使用pt = ...代替*pt = ...

  2. 使用复合文字而不是值(出于同样的原因,str ='a'无效)。

  3. 不能始终保证复合文字能够正常工作,因为其存储寿命取决于标准/实现。 实际上,按上述方法使用它可能会导致编译错误taking address of temporary array

答案 8 :(得分:-1)

可以将字符串变量声明为字符数组char txt[]或使用字符指针char* txt声明。下面说明了字符串的声明和初始化:

char* txt = "Hello";

C string literal

实际上,如上所述,txt是指向字符串文字的第一个字符的指针。

我们是否能够修改(读/写)字符串变量,取决于我们声明它的方式。

  

6.4.5字符串文字(ISO)
  6.如果这些数组的元素具有适当的值,则不确定这些数组是否不同。如果程序尝试修改这样的数组,则行为是不确定的。

实际上,如果我们像以前一样声明字符串txt,即使.rodata,编译器也会在只读数据段txt(依赖于平台)中声明字符串文字。未声明为const char*。所以我们不能修改它。实际上,我们甚至都不应该尝试对其进行修改。在这种情况下,gcc会发出警告(-Wwrite-strings),甚至由于-Werror而失败。在此cas中,最好将字符串变量声明为const指针:

const char* txt = "Hello";

另一方面,我们可以将字符串变量声明为字符数组:

char txt[] = "Hello";

在这种情况下,编译器将安排从字符串文字中初始化数组,以便您可以对其进行修改。

注意:可以使用字符数组,就好像它是指向第一个字符的指针一样。这就是为什么我们可以使用txt[0]*txt语法访问第一个字符的原因。我们甚至可以将字符数组显式转换为指针:

char txt[] = "Hello";
char* ptxt = (char*) txt;