2012-05-10 22 views
19

Nhiều lần tôi muốn có một chức năng để nhận được một số biến của tham số, chấm dứt bởi NULL, ví dụtypesafe varargs trong C với gcc

#define push(stack_t stack, ...) _push(__VARARG__, NULL); 
func _push(stack_t stack, char *s, ...) { 
    va_list args; 
    va_start(args, s); 
    while (s = va_arg(args, char*)) push_single(stack, s); 
} 

Tôi có thể hướng dẫn gcc hoặc kêu vang để cảnh báo nếu foo nhận char* biến phi ? Một cái gì đó tương tự như __attribute__(format), nhưng đối với nhiều đối số của cùng một loại con trỏ.

+2

Nếu tất cả các loại cần phải cùng loại, bạn có xem xét việc chỉ chuyển một mảng trong số đó không? –

+0

Không có mảng gốc trong C89. Bạn không thể vượt qua ví dụ, 'f ({1,2,3,0})' với trình biên dịch MS C. – mikebloch

+3

bạn muốn một cái gì đó mà làm việc với gcc hoặc MS C? Vui lòng gắn thẻ một cách thích hợp. Với C99 có các giải pháp an toàn. –

Trả lời

15

Tôi biết bạn đang nghĩ đến việc sử dụng __attribute__((sentinel)) bằng cách nào đó, nhưng đây là một cá trích đỏ.

Những gì bạn muốn là để làm một cái gì đó như thế này:

#define push(s, args...) ({     \ 
    char *_args[] = {args};      \ 
    _push(s,_args,sizeof(_args)/sizeof(char*)); \ 
}) 

mà kết thúc tốt đẹp:

void _push(stack_t s, char *args[], int argn); 

mà bạn có thể viết chính xác theo cách bạn sẽ hy vọng bạn có thể viết nó!

Sau đó, bạn có thể gọi:

push(stack, "foo", "bar", "baz"); 
push(stack, "quux"); 
+0

Nó sẽ không gây ra sự sao chép không cần thiết của các con trỏ 'char *'? –

+0

@ Chi-Lan - Tùy thuộc vào việc thực hiện '_push()' GCC sẽ loại bỏ các tải không cần thiết. – geocar

+0

Tôi đang cố gắng làm một cái gì đó tương tự như thế này. Tôi thực sự muốn một __attribute đảm bảo an toàn loại của varargs. Về cơ bản tôi muốn chắc chắn rằng họ là tất cả char * 's. Có ai biết làm như thế nào không? –

1

tôi chỉ có thể nghĩ về một cái gì đó như thế này:

#include <stddef.h> 
#include <stdio.h> 
#include <stdlib.h> 

typedef struct tArg 
{ 
    const char* Str; 
    struct tArg* Next; 
} tArg; 

tArg* Arg(const char* str, tArg* nextArg) 
{ 
    tArg* p = malloc(sizeof(tArg)); 
    if (p != NULL) 
    { 
    p->Str = str; 
    p->Next = nextArg; 
    } 
    else 
    { 
    while (nextArg != NULL) 
    { 
     p = nextArg->Next; 
     free(nextArg); 
     nextArg = p; 
    } 
    } 
    return p; 
} 

void PrintR(tArg* arg) 
{ 
    while (arg != NULL) 
    { 
    tArg* p; 
    printf("%s", arg->Str); 
    p = arg->Next; 
    free(arg); 
    arg = p; 
    } 
} 

void (*(*(*(*(*(*(*Print8 
    (const char* Str)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*) 
{ 
    printf("%s", Str); 
    // There's probably a UB here: 
    return (void(*(*(*(*(*(*(*) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*))&Print8; 
} 

int main(void) 
{ 
    PrintR(Arg("HELLO", Arg(" ", Arg("WORLD", Arg("!", Arg("\n", NULL)))))); 
// PrintR(Arg(1, NULL));  // warning/error 
// PrintR(Arg(&main, NULL)); // warning/error 
// PrintR(Arg(0, NULL));  // no warning/error 
// PrintR(Arg((void*)1, NULL)); // no warning/error 

    Print8("hello")(" ")("world")("!")("\n"); 
// Same warning/error compilation behavior as with PrintR() 
    return 0; 
} 
+3

Oh anh trai! Tôi không chắc có cười hay khóc ...;-) –

+0

@ Chi-Lan: C rất đặc biệt. :) –

+0

'Print8' là bit tốt nhất và tệ nhất của C mà tôi từng thấy. – huon

-1

Vấn đề với C variadics là họ đang thực sự bắt vít vào sau đó, không thực sự được thiết kế sang ngôn ngữ. Vấn đề chính là các tham số variadic là ẩn danh, chúng không có tay cầm, không có mã định danh. Điều này dẫn đến các macro VA khó sử dụng để tạo tham chiếu đến các tham số không có tên. Nó cũng dẫn đến sự cần thiết phải nói cho các macro đó khi danh sách variadic bắt đầu và loại tham số nào được mong đợi.

Tất cả thông tin này thực sự phải được mã hóa theo cú pháp thích hợp trong chính ngôn ngữ đó.

Ví dụ, người ta có thể mở rộng cú pháp C hiện với các thông số chính thức sau khi lược, như vậy

void foo (... int counter, float arglist); 

Theo quy ước, tham số đầu tiên có thể cho số lượng đối số và thứ hai cho danh sách đối số. Trong phần thân hàm, danh sách có thể được xử lý cú pháp như một mảng.

Với quy ước như vậy, các tham số variadic sẽ không còn ẩn danh nữa. Trong cơ quan chức năng, bộ đếm có thể được tham chiếu như bất kỳ thông số khác và các yếu tố danh sách có thể được tham chiếu như thể chúng là các phần tử mảng của một tham số mảng, như vậy

void foo (... int counter, float arglist) { 
    unsigned i; 
    for (i=0; i<counter; i++) { 
    printf("list[%i] = %f\n", i, arglist[i]); 
    } 
} 

Với tính năng như vậy được xây dựng vào ngôn ngữ riêng của mình , mỗi tham chiếu đến arglist[i] sau đó sẽ được dịch sang các địa chỉ tương ứng trên khung ngăn xếp. Sẽ không cần phải làm điều này thông qua các macro.

Hơn nữa, số đối số sẽ tự động được trình biên dịch chèn vào, làm giảm thêm cơ hội lỗi.

Một cuộc gọi đến

foo(1.23, 4.56, 7.89); 

sẽ được biên dịch như thể nó đã được viết

foo(3, 1.23, 4.56, 7.89); 

Trong cơ quan chức năng, bất cứ quyền truy cập vào một yếu tố vượt quá con số thực tế của các đối số thực sự trôi qua có thể là kiểm tra tại thời gian chạy và gây ra một lỗi thời gian biên dịch, do đó tăng cường an toàn rất nhiều.

Cuối cùng nhưng không kém phần quan trọng, tất cả các tham số variadic được nhập và có thể được kiểm tra kiểu tại thời gian biên dịch giống như các tham số không-variadic được chọn.

Trong một số trường hợp sử dụng, tất nhiên là mong muốn có các loại xen kẽ, chẳng hạn như khi viết một hàm để lưu trữ khóa và giá trị trong bộ sưu tập. Điều này cũng có thể được cung cấp chỉ đơn giản bằng cách cho phép nhiều tham số chính thức sau khi lược, như vậy

void store (collection dict, ... int counter, key_t key, val_t value); 

Chức năng này sau đó có thể được gọi là

store(dict, key1, val1, key2, val2, key3, val3); 

nhưng sẽ được biên dịch như thể nó đã được viết

store(dict, 3, key1, val1, key2, val2, key3, val3); 

Các loại tham số thực tế sẽ được kiểm tra thời gian biên dịch theo các thông số chính thức của biến thể variadic tương ứng.

Trong cơ thể của hàm quầy một lần nữa sẽ được tham chiếu bởi định danh, khóa và giá trị của nó sẽ được tham chiếu như thể chúng là mảng,

key[i] đề cập đến quan trọng của cặp khoá/giá trị thứ i value[i] đề cập đến giá trị của cặp giá trị thứ i

và các tham chiếu này sẽ được biên soạn đến địa chỉ tương ứng của chúng trên khung ngăn xếp.

Không ai trong số này thực sự khó thực hiện, cũng như chưa từng có. Tuy nhiên, triết lý thiết kế của C chỉ đơn giản là không có lợi cho các tính năng như vậy.

Nếu không có một mạo hiểm trình biên dịch C implementor (hoặc C Preprocessor implementor) đi đầu để thực hiện điều này hoặc một chương trình tương tự nó là không bao giờ chúng ta sẽ thấy bất cứ điều gì của loại hình này trong C.

Vấn đề là folks người quan tâm đến an toàn loại và sẵn sàng đưa vào công việc xây dựng trình biên dịch riêng của họ thường đi đến kết luận rằng ngôn ngữ C vượt quá khả năng cứu hộ và người ta cũng có thể bắt đầu lại bằng ngôn ngữ được thiết kế tốt hơn để bắt đầu.

Tôi đã ở đó, cuối cùng quyết định từ bỏ nỗ lực, sau đó thực hiện một trong các ngôn ngữ của Wirth và thêm các loại variadics an toàn vào thay thế. Tôi có kể từ khi chạy vào những người khác, những người đã nói với tôi về những nỗ lực của họ. Loại variadics an toàn loại trong C dường như sẵn sàng để giữ khó nắm bắt.

+0

Lý do tại sao không phải thực hiện cú pháp, nhưng thay đổi đối với ABI: Ví dụ ABI của x86_64 ABI và i386 fastcall ABI không yêu cầu đối số đó có "địa chỉ" và trong khi công cụ ngăn xếp trên CPU hiện đại thực sự làm rất nhiều cho chi phí của các đối số tràn, chất thải kép của L1 quý giá làm cho nó mất mạng. Nếu bạn chỉ muốn chơi với cú pháp và tính biểu cảm, bạn có thể thử C4 vì nó có ABI (ảo) riêng của nó anyway: https://github.com/rswier/c4 – geocar

+0

Nếu cú ​​pháp này được thiết kế thành ngôn ngữ, thì những người triển khai sẽ phải lập bản đồ cho ABI tương ứng của họ. Không có lý do tại sao bạn không thể làm điều đó. Các tham chiếu được giải quyết tại thời gian biên dịch (ít nhất là về các tham chiếu relocatable) và trình biên dịch có kiến ​​thức về ABI đích. Hơn nữa, không có lý do tại sao một danh sách đối số variadic không thể được ánh xạ tới một bộ đăng ký. Các nỗ lực thực hiện là khá nhiều giống như với việc thực hiện các macro VA. Vấn đề thực sự là các tham số variadic là vô danh. Điều đó đứng trong cách làm cho nó an toàn. – trijezdci

+0

C được thiết kế xung quanh một ABI, không phải là cách khác xung quanh. Nếu bạn muốn một Pascal trông giống như C, bạn có thể xây dựng một trong số 4 c4 như tôi đề xuất - nó tương tự về mặt kiến ​​trúc với nhiều triển khai Pascal. – geocar