仍然学习更多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];
在后台吗?
为什么对此处理方式不同?还是我完全误会了?
答案 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!";
*
之前或之后的空格没有任何区别。第二行可能更易于阅读,因为它使text
是char*
的观点更加明确。 (但是要小心!如果在一行上声明多个变量,这种样式可能会烫伤您!)
至:
int *pt;
*pt = 606; /* Unsafe! */
您可能会说*pt
是int
,606
也是如此,但是更确切地说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个概念:
int *p;
或char *str;
是指针的声明char *str = "some string";
声明指针并对其进行初始化。str = "other string";
将值分配给指针。类似地,p = (int*)606;
会将值606分配给指针。但是,在第一种情况下,该值是合法的,并且指向字符串在静态内存中的位置。在第二种情况下,您可以为p
分配一个任意地址。它可能是合法地址,也可能不是。因此,p = &myint;
或p = malloc(sizeof(int));
是更好的选择。 *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.";
可以(可行的情况下)吗?
请考虑:
char *str = "Sometimes I feel like I'm going crazy.";
等同于
char *str;
str = "Sometimes I feel like I'm going crazy.";
int
的最接近“类似”工作例是(使用compound literal而不是字符串文字)
int *pt = (int[]){ 686, 687 };
或
int *pt;
pt = (int[]){ 686, 687 };
因此,与您无法使用的案例的区别有三点:
使用pt = ...
代替*pt = ...
使用复合文字而不是值(出于同样的原因,str ='a'无效)。
不能始终保证复合文字能够正常工作,因为其存储寿命取决于标准/实现。
实际上,按上述方法使用它可能会导致编译错误taking address of temporary array
。
答案 8 :(得分:-1)
可以将字符串变量声明为字符数组char txt[]
或使用字符指针char* txt
声明。下面说明了字符串的声明和初始化:
char* txt = "Hello";
实际上,如上所述,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;