代码之家  ›  专栏  ›  技术社区  ›  lamas

C字符串混淆

  •  18
  • lamas  · 技术社区  · 15 年前

    我现在正在学习C,对字符数组-字符串有点困惑。

    char name[15]="Fortran";
    

    这没问题-它是一个可以容纳(最多?)15个字符的阵列

    char name[]="Fortran";
    

    C为我计算字符数,这样我就不必-整洁!

    char* name;
    

    可以现在怎么办?我所知道的是,它可以保存大量稍后分配的字符(例如:通过用户输入),但是

    • 为什么他们称之为字符指针?我知道指针是变量的引用
    • 这是“借口”吗?除了在char*中,它还有其他用途吗?
    • 这到底是什么?是指针吗?你如何正确使用它?

    提前感谢,,

    7 回复  |  直到 15 年前
        1
  •  33
  •   t0mm13b    12 年前

    我想这可以这样解释,因为一幅画抵得上千言万语。。。

    char name[] = "Fortran" ,这是一个字符数组,长度在编译时已知,准确地说是7,对吗?错了!它是8,因为“\0”是nul终止字符,所以所有字符串都必须具有该字符。

    char name[] = "Fortran";
    +======+     +-+-+-+-+-+-+-+--+
    |0x1234|     |F|o|r|t|r|a|n|\0|
    +======+     +-+-+-+-+-+-+-+--+ 
    

    在链接时,编译器和链接器给出了符号 name 内存地址为0x1234。 使用下标运算符,即。 name[1] char 数据类型为1字节,这可以解释运行时如何获取该语义的值 name[cnt++] cnt 是一个 int

    如果 name[12] 被处决了?因为数组的边界是从索引/偏移量0(0x1234)到8(0x123B),所以代码要么崩溃,要么得到垃圾。之后的任何东西都不属于 名称 变量,这将被称为缓冲区溢出!

    内存中是0x1234,如示例所示,如果要执行此操作:

    printf("The address of name is %p\n", &name);
    
    Output would be:
    The address of name is 0x00001234
    

    为了简洁起见并与示例保持一致,内存地址为32位,因此您可以看到额外的0。公平吗?好的,让我们继续。

    char *name 是指向类型的指针 烧焦

    编辑: 我们将其初始化为NULL,如图所示 谢谢Dan指出了这个小错误。。。

    char *name = (char*)NULL;
    +======+     +======+ 
    |0x5678| ->  |0x0000|    ->    NULL
    +======+     +======+ 
    

    名称 名称 (0x5678),实际上是 NULL 名称 未知,因此为0x0000。

    现在,记住 这是至关重要的,在编译/链接时,符号的地址是已知的,但在处理任何类型的指针时,指针地址是未知的

    假设我们这样做:

    name = (char *)malloc((20 * sizeof(char)) + 1);
    strcpy(name, "Fortran");
    

    malloc 要为20字节分配内存块,不,它不是21字节,我在大小上添加1的原因是为了“\0”nul终止字符。假设在运行时,给定的地址是0x9876,

    char *name;
    +======+     +======+          +-+-+-+-+-+-+-+--+
    |0x5678| ->  |0x9876|    ->    |F|o|r|t|r|a|n|\0|
    +======+     +======+          +-+-+-+-+-+-+-+--+
    

    所以当你这样做的时候:

    printf("The address of name is %p\n", name);
    printf("The address of name is %p\n", &name);
    
    Output would be:
    The address of name is 0x00005678
    The address of name is 0x00009876
    

    数组和指针是一样的,在这里起作用 '

    char ch = name[1];
    

    运行时发生的情况如下:

    1. 名称
    2. 基于下标值1获取偏移量,并将其添加到指针地址,即0x9877,以检索该内存地址处的值,即“o”,并分配给 ch .

    上面这一点对于理解这种区别是至关重要的,数组和指针之间的区别在于运行时如何获取数据,使用指针时,有一个额外的间接获取。

    ,一系列 将始终衰减为的第一个元素的指针 T型

    当我们这样做时:

    char ch = *(name + 5);
    
    1. 被查到了吗
    2. 获取该符号的内存地址,即0x5678。
    3. 在该地址处,包含另一个地址,即指向内存的指针地址并获取它,即0x9876
    4. 基于值5获取偏移量,并将其添加到指针地址,即0x987A,以检索该内存地址处的值,即“r”,并分配给 .

    顺便说一下,您也可以对字符数组执行此操作。。。

    char name[] = "..."; name[subscript_value] 实际上与*(名称+下标值)相同。

    name[3] is the same as *(name + 3)
    

    自从那句话 *(name + subscript_value) 交换的 就是相反的,,

    *(subscript_value + name) is the same as *(name + subscript_value)
    

    尽管如此,这种做法并不推荐,即使它是相当合法的! )

    3[name]
    

    好的,我如何得到指针的值? 这就是问题所在 * 名称 指针内存地址为0x9878,再次参考上述示例,这是如何实现的:

    char ch = *name;
    

    这意味着,现在获取内存地址0x9878指向的值 中国 将具有“r”的值。这称为解引用。我们刚刚取消了一个 名称 中国 .

    而且,编译器知道 sizeof(char) 是1,因此可以执行如下指针递增/递减操作

    *name++;
    *name--;
    

    指针会自动向上/向下移动一步。

    执行此操作时,假设指针内存地址为0x9878:

    char ch = *name++;
    

    *name的值是多少?地址是多少?答案是 *name 中国 ,指针内存地址为0x9879。

    现在,如果我们释放 通过调用C函数 free 名称 作为参数,即。 free(name)

    +======+     +======+ 
    |0x5678| ->  |0x0000|    ->    NULL
    +======+     +======+ 
    

    是的,内存块被释放并返回到运行时环境,以供另一个即将执行的代码使用 马洛克 .

    现在,这就是 没有指向任何东西,当我们取消引用它时会发生什么,即。

    char ch=*名称;
    

    任何试图取消引用它的行为,都会导致崩溃和烧毁。

    马洛克 自由的 ,如果没有相应的 自由的 ,存在内存泄漏,内存已分配但未释放。

    这就是指针的工作原理,数组与指针的区别,如果你在读一本说它们是一样的教科书,就把那一页撕下来撕碎吧!:)

    我希望这对你理解指针有帮助。

        2
  •  3
  •   Daniel Bingham    15 年前

    实际上,它本身无法容纳大量字符。它本身只能在内存中保存一个地址。如果在创建时为其分配字符,它将为这些字符分配空间,然后指向该地址。您可以这样做:

    char* name = "Mr. Anderson";
    

    这实际上与此基本相同:

    char name[] = "Mr. Anderson";
    

    动态内存是字符指针派上用场的地方。您可以在程序中的任何时间通过以下操作将任意长度的字符串分配给char指针:

    char *name;
    name = malloc(256*sizeof(char));
    strcpy(name, "This is less than 256 characters, so this is fine.");
    

    或者,您可以使用 strdup()

    char *name;
    name = strdup("This can be as long or short as I want.  The function will allocate enough space for the string and assign return a pointer to it.  Which then gets assigned to name");
    

    如果以这种方式使用字符指针并为其分配内存,则必须在重新分配它之前释放名称中包含的内存。这样地:

    if(name)
        free(name);
    name = 0;
    

    在尝试释放其内存之前,请确保检查名称实际上是一个有效点。这就是if语句的作用。

    您看到字符指针在C中大量使用的原因是,它们允许您使用不同大小的字符串重新分配字符串。静态字符数组不能做到这一点。它们也更容易传递。

    char *name;
    
    char joe[] = "joe";
    char bob[] = "bob";
    
    name = joe;
    
    printf("%s", name);
    
    name = bob;
    printf("%s", name);
    

    这是在将静态分配的数组传递给使用字符指针的函数时经常发生的情况。例如:

    void strcpy(char *str1, char *str2);
    

    如果您随后通过:

    char buffer[256];
    strcpy(buffer, "This is a string, less than 256 characters.");
    

    它将通过str1和str2操作这两个函数,str1和str2只是指向缓冲区和字符串文本存储在内存中的指针。

    char *myFunc() {
        char myBuf[64];
        strcpy(myBuf, "hi");
        return myBuf;
    }
    

    那不行。在这种情况下,您必须使用指针并分配内存(如前面所示)。分配的内存将保持不变,即使您超出了函数范围。只是别忘了像前面提到的那样释放它。

    这篇文章比我想象的要更加百科全书,希望能有所帮助。

    编辑删除C++代码。我经常把这两者混在一起,有时会忘记。

        3
  •  2
  •   Robert Groves    15 年前

    char*name只是一个指针。必须在线路的某个地方分配内存,并将该内存的地址存储在 名称

    • 它可以指向一个连续的内存区域,该区域包含许多字符。
    • 如果这些字符恰好以空终止符结尾,那么low将显示一个指向字符串的指针。
        4
  •  2
  •   Stephen Canon    15 年前

    char *name 就其本身而言, 不能容纳任何字符

    字符*名称 只是宣布 name 是一个指针(即,一个值为地址的变量),用于在程序中稍后的某个点存储一个或多个字符的地址。然而,它并没有在内存中分配任何空间来实际保存这些字符,也不能保证这一点 名称 int number number 直到您显式设置它。

    number = 42 ),在声明一个指向char的指针后,您可以稍后将其值设置为一个有效的内存地址,该地址包含您感兴趣的字符或字符序列。

        5
  •  2
  •   mloskot    15 年前

    这确实令人困惑。需要理解和区分的重要事情是 char name[] 声明数组和 char* name 声明指针。这两种动物是不同的。

    char 或否)。正如@which所提到的,您可以同时使用索引运算符或指针算法来访问数组元素。事实上,索引运算符只是指针算法的一种语法糖(同一表达式的另一种表示)。

    char name[15] 使用 sizeof

    char name[15] = { 0 };
    size_t s = sizeof(name);
    assert(s == 15);
    

    但如果你申请 字符*名称

    char* name = 0;
    size_t s = sizeof(name);
    assert(s == 4); // assuming pointer is 4-bytes long on your compiler/machine
    

    此外,char元素数组的两种定义形式是等效的:

    char letters1[5] = { 'a', 'b', 'c', 'd', '\0' };
    char letters2[5] = "abcd"; /* 5th element implicitly gets value of 0 */
    

    / *skip to 'd' letter */
    char* it = letters1;
    for (int i = 0; i < 3; i++)
        it++;
    
        6
  •  2
  •   wich    15 年前

    在C语言中,字符串实际上只是一个字符数组,正如定义所示。然而,从表面上看,任何数组都只是指向其第一个元素的指针,请参见下文了解微妙的复杂性。在C中没有范围检查,变量声明中提供的范围仅对变量的内存分配有意义。

    a[x] *(a + x) ,即指针a的解引用增加x。

    如果您使用以下选项:

    char foo[] = "foobar";
    char bar = *foo;
    

    为了避免混淆并避免误导他人,请在指针和数组之间更为复杂的区别上多说几句,谢谢avakar:

    //sizeof
    sizeof(char*) != sizeof(char[10])
    
    //lvalues
    char foo[] = "foobar";
    char bar[] = "baz";
    char* p;
    foo = bar; // compile error, array is not an lvalue
    p = bar; //just fine p now points to the array contents of bar
    
    // multidimensional arrays
    int baz[2][2];
    int* q = baz; //compile error, multidimensional arrays can not decay into pointer
    int* r = baz[0]; //just fine, r now points to the first element of the first "row" of baz
    int x = baz[1][1];
    int y = r[1][1]; //compile error, don't know dimensions of array, so subscripting is not possible
    int z = r[1]: //just fine, z now holds the second element of the first "row" of baz
    

    最后是一些有趣的琐事;自从 a[x] *(a+x) 实际上,您可以使用例如“3[a]”来访问数组a的第四个元素。也就是说,以下是完全合法的代码,将打印字符串foo的第四个字符“b”。

    #include <stdio.h>
    
    int main(int argc, char** argv) {
      char foo[] = "foobar";
    
      printf("%c\n", 3[foo]);
    
      return 0;
    }
    
        7
  •  1
  •   DigitalRoss    15 年前

    一个是实际的数组对象,另一个是 参考 指针

    令人困惑的是,两者都有第一个字符的地址,但这仅仅是因为一个地址

    差异可以从 &name name ,但在第三种情况下,它是另一种类型,称为 指向char的指针 **char ,它是指针本身的地址。也就是说,它是一个双间接指针。

    #include <stdio.h>
    
    char name1[] = "fortran";
    char *name2 = "fortran";
    
    int main(void) {
        printf("%lx\n%lx %s\n", (long)name1, (long)&name1, name1);
        printf("%lx\n%lx %s\n", (long)name2, (long)&name2, name2);
        return 0;
    }
    Ross-Harveys-MacBook-Pro:so ross$ ./a.out
    100001068
    100001068 fortran
    100000f58
    100001070 fortran