c中无限字符串的strcat

strcat for unlimited string in c


我想在 c 中实现 strcat 但对于无限字符串,该函数将分配最终字符串所需的必要内存,我想到了这个:

char* strcat_ex(const char* str, ...) {
    if(!str)return 0;

    const char* current = str;
    char* res = 0;
    va_list args;
    va_start(args, str);

    // calculate the length of the final string
    size_t len = 0;
    for(; current; current = va_arg(args, char *))
        len += strlen(current);

    // allocate the string
    res = malloc(sizeof(char) * len + 1);
    if(!res)return 0;

    // copy the strings to the final destination
    size_t cur = 0;
    for(; current; current = va_arg(args, char *)) {
        size_t cur_len = strlen(current);
        memcpy_s(res + cur, cur_len, current, cur_len);
    }

    va_end(args);
    return res;
}

在此循环之前

size_t cur = 0;
for(; current; current = va_arg(args, char *)) {
    size_t cur_len = strlen(current);
    memcpy_s(res + cur, cur_len, current, cur_len);
}

您必须使用宏 va_start 重新初始化 args

变量cur在循环中没有改变,总是等于0。所以所有的字符串都被复制到相同的地址res + 0

每次退出程序之前,您都必须调用宏 va_end

函数可以看下面的演示程序所示。

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

char * strcat_ex( const char *s, ... )
{
    char *result = NULL;

    if ( s != NULL )
    {
        va_list ap;

        size_t n = 0;

        va_start( ap, s );

        for ( const char *p = s; p != NULL; p = va_arg( ap, const char * ) )
        {
            n += strlen( p );
        }

        va_end( ap );

        ++n;

        result = ( char * )malloc( n );

        if ( result != NULL )
        {
            n = 0;

            va_start( ap, s );

            for ( const char *p = s; p != NULL; p = va_arg( ap, const char * ) )
            {
                strcpy( result + n, p );
                n += strlen( p );
            }

            va_end( ap );
        }
    }

    return result;
}

int main(void) 
{
    char *s;

    s = strcat_ex( "1", "2", "3", "4", "5", NULL );

    puts( s );

    free( s );

    s = strcat_ex( "Hello", " ", "World!", NULL );

    puts( s );

    free( s );

    return 0;
}

程序输出为

12345
Hello World!

您可以添加一个检查,确保所有字符串的总长度不大于 size_t 类型对象中可存储的最大值。那就是有np溢出。

首先,一些一般性评论。

if(!str)return 0;

虽然常量 0 保证等同于空指针,但我不相信检查指针是否为假。这可能不是常见环境的问题,但如果您来自 Windows 背景,那么戳 C 兼容性龙 尤其是 是个坏习惯很容易沉迷于 Microsoft 扩展,尤其是 如果您对 C 标准没有广博的了解。不要戳边缘情况。如果您的意思是 null,请使用 NULL.

说到 Microsoft 扩展,memcpy_s 是一个非标准的 Microsoft 扩展。使用 memcpy 或提供兼容性包装器。

va_arg 必须在每次使用时重新初始化,它不会自行重启。将 va_startva_end 想象成 va_args 周围的大括号。基本模式是这样的:

va_start(args, first);
for(
    current = first;
    current != NULL;
    current = va_arg(args, char *)
) {
    ...
}
va_end(args);

你不知道有多少可变参数,va_arg 会继续读取堆栈上的垃圾。您要么必须传入容易出错的参数数量,要么使用标记值,例如将最后一个参数设为 NULL。

我会考虑将其命名为 strcat* 以外的名称,因为它具有不同的界面。像 strcat 这样使用它会导致难以调试的错误。 strcat 的形式是 strcat( dest, src ),而您的函数是 dest = strcat_ex( src, src, ... )。有人写strcat_ex( dest, src, src, ... ).

会很有诱惑力

想到的名字是strjoinjoin 其他语言的函数包括提供定界符的功能。这表明您可以向该函数添加一个有用的功能,您通常希望使用 ", "" " 等分隔符连接字符串,这将是第一个参数。


为避免必须读取所有字符串两次,您可以利用参数数量非常少的优势。参数的数量受堆栈大小和程序员的理智限制。因此,多次遍历少量参数比多次调用字符串 strlen 更便宜。

您需要对参数进行三个循环。

  1. 找出有多少参数。
    • 分配一个数组来保存它们的长度。
  2. 存储字符串长度及其总长度。
    • 分配目的地。
  3. 复制字符串。
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

char* strjoin(const char* first, ...) {
    if( first == NULL ) {
        return NULL;
    }

    va_list args;
    int i;
    const char *current;

    // 1. Figure out how many arguments there are.
    va_start(args, first);
    size_t num_args = 0;
    for(
        current = first;
        current != NULL;
        current = va_arg(args, char *)
    ) {
        num_args++;
    }
    va_end(args);

    //   * Allocate an array to hold their lengths.
    size_t str_sizes[num_args];

    // 2. Store the string lengths and their total length.
    size_t dest_size = 0;
    va_start(args, first);
    for(
        i = 0, current = first;
        current != NULL;
        i++, current = va_arg(args, char *)
    ) {
        str_sizes[i] = strlen(current);
        dest_size += str_sizes[i];
    }
    va_end(args);

    //     * Allocate the destination.
    char *dest = malloc(dest_size + 1);
    if( dest == NULL ) {
        return NULL;
    }

    // 3. Copy the strings.
    char *dest_pos = dest;
    va_start(args, first);
    for(
        i = 0, current = first;
        current != NULL;
        i++, current = va_arg(args, char *)
    ) {
        memcpy( dest_pos, current, str_sizes[i] );
        dest_pos += str_sizes[i];
    }
    va_end(args);

    // Add the final null byte
    dest_pos[0] = '[=12=]';

    return dest;
}

int main() {
    char *all = strjoin( "foo", "bar", "baz", "biff", "wibble", NULL );
    puts(all);
}