如何获取char*(字符数组)的真实和总长度?

79

对于一个char []数组,我可以通过以下方式轻松获取其长度:

char a[] = "aaaaa";
int length = sizeof(a)/sizeof(char); // length=6

然而,我不能像这样获取 char * 的长度:

char *a = new char[10];
int length = sizeof(a)/sizeof(char);

因为我知道这里的 a 是一个指针,所以这里的 length 始终为 4(或其他系统中的某个数字)。

我的问题是,之后如何获取 char * 的长度?我知道有人可能会质疑我已经知道它是 10,因为你刚刚创建了它。但我想知道这是因为获取其长度的步骤可能与其创建的步骤相隔很远,而我不想返回长长的路程来检查这个数字。此外,我还想知道它的真实长度。

具体来说:

  • 如何获取它的真实长度 length=5
  • 如何获取它的总长度 length=10

对于以下示例:

char *a = new char[10]; 
strcpy(a, "hello");

15
strlen函数是用来计算字符串长度的,它接受一个字符串作为参数并返回该字符串中字符的数量,不包括结尾的空字符。 - Kiril Kirov
3
@KirilKirov这并不会给出数组的长度。嗯,特殊情况下会有,但这很少见。 - juanchopanza
2
这个问题以前已经回答过了,指针并不保存它所指向的数据块的大小信息(如果它指向一个数组),只保存它在内存中的起始位置。 - A Person
6
你可以使用 std::vector<char> ,这样你就可以获取大小,并且内存管理已经为你完成。 - Jarod42
2
请问您是在询问哪种编程语言?如果是C语言,那么您不能使用new;如果是C++,则应该使用更高级的抽象,如vector来解决这个问题。 - Mike Seymour
显示剩余15条评论
16个回答

72

你做不到百分之百的准确性。指针只有自己的长度/大小,它只是指向内存中一个char的特定位置。如果该char是字符串的一部分,则可以使用strlen确定当前指向的字符后面跟随的字符,但这并不意味着在你的情况下数组那么大。
基本上:

指针不是数组,因此它不需要知道数组的大小。指针可以指向单个值,因此即使没有数组,指针也可以存在。它甚至不关心它所指向的内存位于何处(只读、堆栈等都没关系)。指针除了自己的长度外,没有别的长度。指针只是存在…
考虑以下内容:

char beep = '\a';
void alert_user(const char *msg, char *signal); //for some reason
alert_user("Hear my super-awsome noise!", &beep); //passing pointer to single char!

void alert_user(const char *msg, char *signal)
{
    printf("%s%c\n", msg, *signal);
}

指针可以是单个字符,也可以是数组的开头、末尾或中间...
把字符看作结构体。有时你会在堆上分配一个单独的结构体。这也会创建一个不带数组的指针。

仅使用指针来确定它所指向的数组大小是不可能的。你能做到的最接近的方法是使用 calloc 并通过指针计算连续的\ 0字符数。当然,一旦你给该数组的键分配或重新分配内容,这种方法就失效了。如果数组外的内存恰好也包含 \ 0 ,则该方法也会失败。因此,使用此方法是不可靠、危险且通常很愚蠢的做法。不要这样做。

另一种比喻:
把指针看作一个路标,它指向城镇X。这个路标不知道那个城镇长什么样,也不知道(或不关心)谁住在那里。它的工作是告诉你在哪里找到城镇X。它只能告诉你那个城镇有多远,但不能告诉你它有多大。这些信息对于路标来说被认为是无关紧要的。只有通过观察城镇本身而不是指向它方向的路标才能找到这些信息。

因此,使用指针时,唯一能做的事情是:

char a_str[] = "hello";//{h,e,l,l,o,\0}
char *arr_ptr = &a_str[0];
printf("Get length of string -> %d\n", strlen(arr_ptr));

当然,这只适用于以\0结尾的数组/字符串。

附带说一句:

int length = sizeof(a)/sizeof(char);//sizeof char is guaranteed 1, so sizeof(a) is enough

实际上是将 sizeof 的返回类型 size_t 分配给一个 int,最好写成:

size_t my_size = sizeof(my_var);
int my_int_size = static_cast<int>(my_size);
size_t length = sizeof(a)/sizeof(*a);//best use ptr's type -> good habit

由于 size_t 是一个无符号类型,如果 sizeof 返回更大的值,length 的值可能会超出你的预期...


@herohuyongtao: 使用指针无法获取 _最大长度_。在你的片段中,new char[10] 分配了 10 个字符,并将指针分配给 a。你可以在 strcpy 之后使用 strlen(a),它将返回 5,但不可能获得 10,除非你做类似于 char *a = calloc(10, sizeof *a); 然后 for(i=0;a[i] == '\0';++i); 之后,i-1 可以给出分配内存的总长度,如果紧邻分配块的内存也没有意外地保留 \0。这很危险而且不好。但你是在使用 C++:用 std::stringstd::vector - Elias Van Ootegem
只是出于好奇,a[0]==0[a]==*a吗?为什么使用sizeof(*a)比使用sizeof(a[0])更好呢?除非你的意思是它比单独使用sizeof(a)更好... - A Person
@Siidheesh:严格来说,a[0] == 0[a] == *(a+0),但使用sizeof *a的主要原因是在使用char以外的其他类型或在自定义分配器中使用指向指针的指针时。考虑以下代码void my_alloc(void **ptr, size_t size) { (*ptr) = malloc(size*sizeof(*(*ptr)));},这将适用于分配结构体、整数、字符等任何类型,而sizeof(type)则需要您知道类型。 - Elias Van Ootegem
@APerson:我的意思是使用sizeof *a比使用sizeof <type>更好,而且比sizeof a好得多(这不总是你想要的)。就个人而言,我也更喜欢*a而不是a[0],因为它非常清楚地表明了指针被解引用。在审查代码或查找段错误的原因时,这些行是我首选的。当我看到a[0]时,我可能会错误地假设a是一个本地数组变量,而不是一个NULL指针。 - Elias Van Ootegem

22
如果 char * 以 0 结尾,你可以使用 strlen 来获取字符串长度。
否则,无法确定该信息。

5
strlen 无法可靠地给出数组的长度。 - juanchopanza
1
除非您正在使用特定的编译器并找到有关已分配内存的数据存储位置(毕竟,为了使内存分配正常工作,必须在某个地方存储为该特定位置分配的内存量,以便它不会被另一个分配所覆盖,并且free函数可以正常工作)。 - JAB
8
strlen 函数不会计算 '\0' 字符。 - Maroun
@JAB 这是一个很好的评论,尽管我不知道访问该信息的任何标准方法。 - Olotiar
对于我的示例(已更新),由于strlen只能获取5,如何获得10? - herohuyongtao
显示剩余3条评论

7

只有两种方法:

  • 如果您的char *指向的内存表示C字符串(即,它包含字符,其中一个0字节标记它的结尾),则可以使用strlen(a)

  • 否则,您需要将长度存储在某个地方。实际上,指针只指向一个char。但是我们可以将其视为指向数组的第一个元素。由于该数组的“长度”未知,因此需要在某个地方存储该信息。


4
  • 在C++中:

只需使用std::vector<char>,该容器可以为您保留(动态)大小。(附加功能:免费内存管理)。

或者使用std::array<char, 10>来保留(静态)大小。

  • 在纯C中:

创建一个结构来存储信息,类似于:

typedef struct {
    char* ptr;
    int size;
} my_array;

my_array malloc_array(int size)
{
    my_array res;
    res.ptr = (char*) malloc(size);
    res.size = size;
    return res;
}

void free_array(my_array array)
{
    free(array.ptr);
}

4

sizeof操作符的作用是返回所需存储操作数的字节数。

存储char类型所需的空间始终为1字节。因此,sizeof(char)始终返回1。

char a[] = "aaaaa";

int len1 = sizeof(a)/sizeof(char); // length = 6
int len2 = sizeof(a);              // length = 6;

这对于len1len2都是相同的,因为这个除以1并不影响等式。 len1len2都具有6的值的原因与字符串终止字符'\0'有关。该字符也是一个字符,会增加长度。因此,您的长度将是6而不是您预期的5。
char *a = new char[10];
int length = sizeof(a)/sizeof(char);

你已经提到长度在这里是4,这是正确的。再次说明,sizeof 运算符返回操作数的存储量,在你的情况下它是一个指针a。一个指针需要4个字节的存储空间,因此在这种情况下长度为4。由于你可能将其编译为32位二进制文件。如果你创建了一个64位二进制文件,则结果将为8。
这个解释可能已经存在了。只是想分享我的意见。

3

char *a = new char[10];

我的问题是如何获取char *的长度

非常简单 :) 只需要添加一条语句即可。

size_t N = 10;
char *a = new char[N];

现在你可以获取已分配数组的大小。
std::cout << "The size is " << N << std::endl;

很多人在这里提到了C标准函数std::strlen。但它不能返回字符数组的实际大小,它只返回存储的字符串字面值的大小。

区别在于以下内容。如果以您的代码片段为例。

char a[] = "aaaaa";
int length = sizeof(a)/sizeof(char); // length=6

如果按照你的代码,使用std::strlen(a)会返回5而不是6。

所以结论很简单:如果需要动态分配字符数组,请考虑使用类std::string。它有方法size和它的同义词length,可以随时获取数组的大小。

例如

std::string s( "aaaaa" );

std::cout << s.length() << std::endl;

或者

std::string s;
s.resize( 10 );

std::cout << s.length() << std::endl;

3

仅仅有指针是不行的。你需要保持对传递给 new[] 的长度的控制,或者更好的方法是使用 std::vector 来跟踪长度,并在完成后释放内存。

注意:此答案仅涉及C++,而不是C。


假设他只使用C++。如果代码还需要作为C程序工作,那么std::vector将不会有太大的帮助。 - JAB
2
@JAB:哦,是的,我刚注意到这个问题在同时询问两种语言。我希望人们能停止这样做。 - Mike Seymour
3
如果他在使用 new,那么他就不可能在使用C语言。 - John Dibling
@JohnDibling 是的,但问题标记了C++C(尽管现在不再是这样)。 - JAB

2
您可以实现自己的newdelete函数,以及一个额外的get-size函数:
#define CEIL_DIV(x,y) (((x)-1)/(y)+1)

void* my_new(int size)
{
    if (size > 0)
    {
        int* ptr = new int[1+CEIL_DIV(size,sizeof(int))];
        if (ptr)
        {
            ptr[0] = size;
            return ptr+1;
        }
    }
    return 0;
}

void my_delete(void* mem)
{
    int* ptr = (int*)mem-1;
    delete ptr;
}

int my_size(void* mem)
{
    int* ptr = (int*)mem-1;
    return ptr[0];
}

或者,您可以以类似的方式覆盖newdelete运算符。


2
+1 分给你的创意,尽管我不建议在实际应用中这样做。你还需要实现复制和调整大小功能。使用 C++ 时,有更好的方法来解决这个问题。 - DarkDust

1

好的,我知道这是一个古老的帖子。 根据我的实验,像这样的赋值语句:

char* str = "blah";

确实会追加一个空字符。这段代码:

char* str = "blah";
cout << str;
cout << strlen(str);

输出:

blah

4

如果我更改分配的字符数,它仍然可以工作。由于赋值似乎在末尾添加了\0,我不理解上面的警告(“strlen仅在有nul char时起作用”等)。我是否遗漏了什么?


1
字符串字面量会自动以空字符结尾。请参考https://en.cppreference.com/w/cpp/language/string_literal - BuvinJ
1
话虽如此,依我之见,将代码编写为传递 char* 并假定其肯定以 null 结尾的做法真是糟糕透顶。如果需要一个"字符串",我认为99% 的情况下使用 std::string(或者其他库中的字符串类型)更好。可以通过引用或指针进行传递,以避免不必要的复制(如果这是个问题的话)。与使用 char * 原始类型相比,使用一点额外内存或处理包装对象的劣势通常可以忽略不计,而风险和麻烦则大得多。 - BuvinJ

1
这可能听起来很邪恶™,我没有测试过,但是在分配数组时将所有值初始化为'\0',然后使用strlen()如何?这将给你所谓的真实值,因为它会在遇到第一个'\0'时停止计数。
嗯,现在我想想,除非你想陷入一堆脏内存,请不要永远™这样做。
此外,对于分配的内存或总内存,如果您的环境提供了以下函数,则可以使用它们:

1
如果你将 char* 数组的所有值设置为 '\0',那么 strlen() 将返回 0。 - Bill Lynch
@sharth,确实是他想要的“真实值”,因为你提到的只是在内存尚未使用时的情况。假设他通过将元素相等化来改变它们的“真实值”,那么从调用strlen()中他将不再得到0。 - Siddharth
当char数组包含二进制信息(例如图像指针)时,这可能会导致一些问题:数组中的数据可能包含'\0'字节,在这种情况下,数据长度大于strlen(data)。 - Theforgotten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接