Vượt qua số lượng đối số khác nhau xung quanh


333

Giả sử tôi có một hàm C có số lượng đối số thay đổi: Làm thế nào tôi có thể gọi một hàm khác có số lượng đối số thay đổi từ bên trong nó, chuyển tất cả các đối số đã vào hàm đầu tiên?

Thí dụ:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }

4
Ví dụ của bạn có vẻ hơi lạ đối với tôi, ở chỗ bạn chuyển fmt cho cả format_opes () và fprintf (). Format_ String () có nên trả về một chuỗi mới không?
Kristopher Johnson

2
Ví dụ không có ý nghĩa. Nó chỉ là để hiển thị các phác thảo của mã.
Vicent Marti

162
"nên được googled": Tôi không đồng ý. Google có rất nhiều tiếng ồn (thông tin không rõ ràng, thường gây nhầm lẫn). Có một câu trả lời tốt (được bình chọn, được chấp nhận) trên stackoverflow thực sự có ích!
Ansgar

71
Chỉ để cân nhắc: Tôi đã đến với câu hỏi này từ google, và bởi vì nó là stack stack rất tự tin rằng câu trả lời sẽ hữu ích. Vậy hãy hỏi đi!
tenpn

32
@Ilya: nếu không ai từng viết ra những thứ bên ngoài Google, sẽ không có thông tin để tìm kiếm trên Google.
Erik Kaplun

Câu trả lời:


211

Để vượt qua các dấu chấm lửng trên, bạn phải chuyển đổi chúng thành va_list và sử dụng va_list đó trong hàm thứ hai của bạn. Đặc biệt;

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}

3
Mã được lấy từ câu hỏi và thực sự chỉ là một minh họa về cách chuyển đổi dấu chấm lửng chứ không phải bất cứ thứ gì có chức năng. Nếu bạn nhìn vào nó format_stringcũng sẽ không hữu ích, vì nó sẽ phải thực hiện các sửa đổi tại chỗ đối với fmt, điều này chắc chắn không nên được thực hiện. Các tùy chọn sẽ bao gồm loại bỏ hoàn toàn format_ chuỗi và sử dụng vfprintf, nhưng điều đó đưa ra các giả định về định dạng thực sự làm gì, hoặc có format_ chuỗi trả về một chuỗi khác. Tôi sẽ chỉnh sửa câu trả lời để hiển thị cái sau.
SmacL

1
Nếu chuỗi định dạng của bạn tình cờ sử dụng các lệnh chuỗi định dạng tương tự như printf, bạn cũng có thể nhận một số trình biên dịch như gcc và clang để đưa ra cảnh báo nếu chuỗi định dạng của bạn không tương thích với các đối số thực tế được truyền vào. Xem thuộc tính hàm GCC ' định dạng 'để biết thêm chi tiết: gcc.gnu.org/onlinesocs/gcc/Function-Attribut.html .
Doug Richardson

1
Điều này dường như không hoạt động nếu bạn vượt qua các đối số hai lần liên tiếp.
fotanus

2
@fotanus: nếu bạn gọi một hàm với argptrvà hàm được gọi sử dụng argptrtất cả, điều an toàn duy nhất cần làm là gọi va_end()và sau đó khởi động lại va_start(argptr, fmt);để khởi động lại. Hoặc bạn có thể sử dụng va_copy()nếu hệ thống của bạn hỗ trợ nó (C99 và C11 yêu cầu; C89 / 90 thì không).
Jonathan Leffler

1
Xin lưu ý rằng nhận xét của @ ThomasPadron-McCarthy đã hết hạn và fprintf cuối cùng vẫn ổn.
Frederick

59

Không có cách nào gọi (ví dụ) printf mà không biết bạn đang chuyển qua bao nhiêu đối số, trừ khi bạn muốn tham gia vào các thủ thuật nghịch ngợm và không di động.

Các giải pháp thường được sử dụng là luôn cung cấp một dạng thay thế các chức năng vararg, vì vậy printfvprintfmà phải mất một va_listở vị trí của .... Các ...phiên bản chỉ là bao bọc xung quanh các va_listphiên bản.


Lưu ý rằng vsyslogkhông POSIX tuân thủ.
patryk.beza

53

Chức năng Variadic có thể nguy hiểm . Đây là một mẹo an toàn hơn:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});

11
Thậm chí tốt hơn là mẹo này: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III

5
@ RichardJ.RossIII Tôi ước bạn sẽ mở rộng nhận xét của mình, nó khó đọc như thế này, tôi không thể đưa ra ý tưởng đằng sau mã và nó thực sự trông rất thú vị và hữu ích.
Penelope

5
@ArtOfWarfare tôi không chắc là tôi đồng ý rằng đó là một bản hack tồi, Rose có một giải pháp tuyệt vời nhưng nó liên quan đến việc gõ func ((type []) {val1, val2, 0}); Cảm giác thật cồng kềnh, nếu bạn có #define func_short_cut (...) func ((loại []) { VA_ARGS }); sau đó bạn chỉ cần gọi func_short_cut (1, 2, 3, 4, 0); cung cấp cho bạn cú pháp tương tự như một hàm matrixdic bình thường với lợi ích bổ sung của thủ thuật gọn gàng của Rose ... vấn đề ở đây là gì?
chrispepper1989

9
Điều gì nếu bạn muốn vượt qua 0 như là một đối số?
Julian Gold

1
Điều này đòi hỏi người dùng của bạn phải nhớ gọi bằng cách kết thúc 0. Làm thế nào an toàn hơn?
cp.engr

29

Trong C ++ 0x tuyệt vời, bạn có thể sử dụng các mẫu matrixdic:

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}

Đừng quên rằng các mẫu matrixdic vẫn chưa có sẵn trong Visual Studio ... điều này có thể không gây lo ngại cho bạn!
Tom Swirly

1
Nếu bạn đang sử dụng Visual Studio, các mẫu có thể được thêm vào Visual Studio 2012 bằng CTP tháng 11 năm 2012. Nếu bạn đang sử dụng Visual Studio 2013, bạn sẽ có các mẫu matrixdic.
2023370

7

Bạn có thể sử dụng lắp ráp nội tuyến cho cuộc gọi chức năng. (trong mã này tôi giả sử các đối số là ký tự).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }

4
Không di động, phụ thuộc vào trình biên dịch và ngăn chặn tối ưu hóa trình biên dịch. Giải pháp rất tệ.
Geoffroy

4
Mới mà không xóa quá.
dùng7116

8
Ít nhất điều này thực sự trả lời câu hỏi, mà không xác định lại câu hỏi.
lama12345

6

Bạn cũng có thể thử macro.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}

6

Mặc dù bạn có thể giải quyết việc chuyển định dạng bằng cách lưu trữ nó trong bộ đệm cục bộ trước, nhưng điều đó cần ngăn xếp và đôi khi có thể là vấn đề cần giải quyết. Tôi đã thử làm theo và nó có vẻ hoạt động tốt.

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

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

Hi vọng điêu nay co ich.


2

Giải pháp của Ross đã dọn dẹp một chút. Chỉ hoạt động nếu tất cả các đối số là con trỏ. Ngoài ra, việc triển khai ngôn ngữ phải hỗ trợ xóa dấu phẩy trước đó nếu __VA_ARGS__trống (cả Visual Studio C ++ và GCC đều làm).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}

0

Giả sử bạn có một hàm biến đổi điển hình mà bạn đã viết. Bởi vì ít nhất một đối số được yêu cầu trước đối số biến đổi ..., bạn phải luôn viết thêm một đối số trong cách sử dụng.

Hay bạn

Nếu bạn bọc hàm matrixdic của mình trong một macro, bạn không cần phải có đối số trước. Xem xét ví dụ này:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

Điều này rõ ràng thuận tiện hơn nhiều, vì bạn không cần chỉ định đối số ban đầu mỗi lần.


-5

Tôi không chắc chắn nếu điều này làm việc cho tất cả các trình biên dịch, nhưng nó đã làm việc cho đến nay đối với tôi.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

Bạn có thể thêm ... vào Internal_func () nếu bạn muốn, nhưng bạn không cần nó. Nó hoạt động vì va_start sử dụng địa chỉ của biến đã cho làm điểm bắt đầu. Trong trường hợp này, chúng tôi sẽ cung cấp cho nó một tham chiếu đến một biến trong func (). Vì vậy, nó sử dụng địa chỉ đó và đọc các biến sau đó trên ngăn xếp. Hàm Internal_func () đang đọc từ địa chỉ ngăn xếp của func (). Vì vậy, nó chỉ hoạt động nếu cả hai chức năng sử dụng cùng một phân đoạn ngăn xếp.

Các macro va_start và va_arg thường sẽ hoạt động nếu bạn cung cấp cho chúng bất kỳ var nào làm điểm bắt đầu. Vì vậy, nếu bạn muốn, bạn có thể chuyển con trỏ đến các chức năng khác và sử dụng chúng. Bạn có thể làm cho macro của riêng bạn đủ dễ dàng. Tất cả các macro làm là địa chỉ bộ nhớ typecast. Tuy nhiên, làm cho chúng hoạt động cho tất cả các trình biên dịch và gọi các quy ước là khó chịu. Vì vậy, nói chung dễ sử dụng những cái đi kèm với trình biên dịch.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.